4. EL(Expression Language)
EL은JSP에서 값을 더 짧고 쉽게 꺼내서 표현하기 위한 문법이다.
복잡한Java코드를 길게 적지 않아도, 화면에 보여 줄 값을 간단한 형태로 출력할 수 있게 만든 표현용 문법이라고 보면 된다.
앞에서 본 표현식 태그는<%= %>형태로 값을 출력했다.
이 방식도 충분히 쓸 수 있지만, 요청 파라미터나 저장된 객체 값을 자주 꺼내야 할수록 코드가 길어지고 읽기가 불편해질 수 있다.
그래서 이런 부분을 더 단순하게 표현하려고EL이 나온 것이다.
왜
EL이 필요한가표현식 태그는
JSP동작 원리를 이해하기에는 좋다.
하지만 화면에 값을 보여 줄 때마다request.getParameter()같은Java코드를 계속 적어야 하므로, 화면 코드 안에 처리 코드가 많이 섞이게 된다.
반면EL은 같은 값을${param.message}처럼 더 짧게 꺼낼 수 있다.
즉,EL은 화면에서 자주 꺼내는 값을 더 간단한 문법으로 표현하도록 만든 도구라고 이해하면 된다.
- 표현식 태그는
Java코드 형태로 값을 출력한다.EL은 값을 더 짧고 읽기 쉽게 표현한다.- 특히 요청 파라미터나 저장된 값을 꺼낼 때 훨씬 간단해진다.
즉,
EL은 새로운 서버 기술이라기보다,JSP에서 화면에 필요한 값을 더 간단하게 꺼내기 위한 표현 문법이라고 보면 된다.
화면 코드와 처리 코드가 왜 분리되는가
JSP는 결국 브라우저에 보여 줄 화면을 만드는 파일이다.
그래서 화면 파일 안에는 화면 구조가 잘 보이는 것이 중요하다.
그런데 값을 꺼낼 때마다 긴Java코드를 계속 적기 시작하면, 화면을 만드는 코드와 값을 처리하는 코드가 한데 섞이게 된다.
그러면 나중에 다시 읽을 때도 “이 줄이 화면 구조인지, 값 처리 코드인지”가 한눈에 잘 안 들어올 수 있다.
이때EL을 쓰면 화면에서 필요한 값을 짧게 표현할 수 있으므로,JSP가 훨씬 화면 문서답게 보인다.
즉,EL은 단순히 짧게 쓰기 위한 문법이 아니라, 화면 코드와 처리 코드를 덜 뒤섞이게 만들어서 읽기 쉽게 해 주는 문법이라고 이해하면 된다.
표현식 태그와 무엇이 다른가
표현식 태그와
EL은 둘 다 결과를 화면에 보여 준다는 점에서는 비슷하다.
하지만 표현식 태그는Java코드 중심이고,EL은 값 표현 중심이라는 점이 다르다.
표현식 태그는 자바 메서드를 직접 호출해서 값을 가져온다.
반면EL은 이미 준비된 문법으로 값을 바로 꺼낸다.
즉, 화면 문서 안에서는EL이 더 짧고, 어떤 값을 꺼내는지도 더 빨리 보인다.
- 표현식 태그는
Java코드 기반이다.EL은 값 표현에 맞춘 간단한 문법이다.- 같은 값을 꺼내도
EL이 더 짧고 가독성이 좋다.즉, 둘 다 출력은 가능하지만, 화면 중심으로 보면
EL이 더 단순하고 정리된 형태라고 이해하면 된다.
EL도 연산할 수 있다
EL은 단순히 저장된 값만 꺼내는 문법이 아니다.
산술 연산, 비교 연산,mod, 삼항 연산처럼 화면에서 자주 필요한 계산도 함께 할 수 있다.
즉, 값을 출력하기 전에 간단한 계산이나 비교를 같은 문장 안에서 처리할 수 있다.
- 더하기, 빼기, 나누기 같은 산술 연산이 가능하다.
eq,lt,gt,le,ge같은 비교 연산도 가능하다.mod연산과 삼항 연산도 사용할 수 있다.즉,
EL은 값을 꺼내는 문법이면서, 화면에서 필요한 간단한 계산까지 처리할 수 있는 표현 문법이다.
예제로 보면
EL연산이 어떻게 동작하는지 보인다아래 예제는
EL이 어떤 연산을 할 수 있는지 한 번에 보여 준다.
숫자 계산, 비교,mod, 삼항 연산, 문자열 결합 결과까지 모두 화면에 출력하고 있다.<!-- elexam1.jsp --> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>EL 테스트</title> </head> <body> <h2>EL의 연산자들</h2> <hr> \${200+100} : ${200+100} <!-- 산술 연산 --> <br> \${200-100} : ${200-100} <!-- 산술 연산 --> <br> \${200/100} : ${200/100} <!-- 산술 연산 --> <br> \${200>100} : ${200>100} <!-- 비교 연산 --> <br> \${200==100} : ${200==100} <!-- 비교 연산 --> <br> \${200!=100} : ${200!=100} <!-- 비교 연산 --> <br> \${40 mod 5 } : ${40 mod 5 } <!-- 나머지 연산 --> <br> \${ 10 eq 10 } : ${ 10 eq 10 } <!-- eq 비교 --> <br> \${ 10 lt 10 } : ${ 10 lt 10 } <!-- lt 비교 --> <br> \${ 10 gt 10 } : ${ 10 gt 10 } <!-- gt 비교 --> <br> \${ 10 le 10 } : ${ 10 le 10 } <!-- le 비교 --> <br> \${ 10 ge 10 } : ${ 10 ge 10 } <!-- ge 비교 --> <br> \${10 > 5?'A':'B'} : ${10 > 5?'A':'B'} <!-- 삼항 연산 --> <br> \${100 + 200 + 300 } : ${100 + 200 + 300 } <!-- 숫자 덧셈 --> <br> \${100 += 200 += 300 } : ${100 += 200 += 300 } <!-- 문자열처럼 이어 붙는 결과 확인 --> <br> \${"EL" += 12 += 34 += "-문자열 결합연산" } : ${"EL" += 12 += 34 += "-문자열 결합연산" } <!-- 문자열 결합 결과 확인 --> </body> </html>이 코드에서 앞에 붙은
\${ ... }는 실제EL을 실행하려는 것이 아니라, 문자 그대로${...}모양 자체를 화면에 보여 주기 위한 표시다.
즉, 왼쪽에는 “어떤 식을 썼는지”를 그대로 보여 주고, 오른쪽에는 그 식이 실제로 계산된 결과를 보여 주는 구조라고 보면 된다.
또 여기서+=는 자바에서 변수에 값을 다시 대입하는 연산처럼 보일 수 있다.
하지만 이 예제에서는 값을 저장해 두는 흐름보다, 표현 결과가 어떻게 이어져 보이는지 확인하는 예제로 이해하는 것이 더 자연스럽다.
즉, 지금 단계에서는 “화면에서 결과가 문자열처럼 이어져 보일 수도 있구나” 정도로 보면 충분하다.
이 예제의 핵심은EL이 단순히 저장된 값만 꺼내는 문법이 아니라, 화면에서 필요한 간단한 계산과 비교까지 처리할 수 있다는 점이다.
즉, 결과를 표현하는 단계에서 필요한 연산을 함께 넣을 수 있다.
출력 결과는 아래와 같다.
이 이미지를 보면
EL이 산술 연산, 비교 연산,mod, 삼항 연산, 문자열 결합까지 화면에서 바로 처리할 수 있다는 점이 드러난다.
즉,EL은 단순 출력 문법이 아니라, 표현 단계에서 필요한 계산까지 담당할 수 있다.
요청 파라미터도 더 쉽게 꺼낼 수 있다
EL이 특히 편해지는 구간은 요청 파라미터를 꺼낼 때다.
표현식 태그로는request.getParameter("message")처럼 적어야 하지만,EL에서는${param.message}또는${param["message"]}처럼 더 짧게 적을 수 있다.
즉, 자주 쓰는 요청값 추출을 훨씬 간단하게 바꿔 준다.
또EL은empty같은 문법도 지원해서, 값이 들어 있는지 여부를 바로 확인할 수도 있다.
이 점 때문에 단순 출력뿐 아니라 값 존재 여부를 판단하는 데도 편하다.
${param.message}로 요청 파라미터를 꺼낼 수 있다.${param["message"]}처럼 대괄호 방식도 가능하다.${!empty param.message}로 값 존재 여부를 확인할 수 있다.즉,
EL은 요청값을 표현식 태그보다 더 간단하게 읽고 판단할 수 있게 해 준다.
empty와!empty는 무엇을 검사하는가
empty는 값이 비어 있는지 검사하는 문법이다.
여기서 비어 있다는 말은 단순히 빈 문자열만 뜻하는 것이 아니라,null이거나 길이가 없는 상태까지 함께 포함해서 이해하면 된다.
반대로 앞에!를 붙인!empty는 “비어 있지 않다”는 뜻이다.
즉,${!empty param.message}는message값이 실제로 들어왔는지 확인하는 표현이라고 보면 된다.
여기서 초보자가 헷갈리기 쉬운 점은 값이 없을 때 왜 오류가 아니라 그냥 비어 보이느냐는 것이다.
EL은 값을 찾지 못했다고 해서 바로 화면을 깨뜨리기보다, 없으면 없는 상태에 맞는 결과를 조용히 보여 주는 쪽에 가깝다.
그래서 존재 여부는false가 나오고, 값 출력 줄은 빈칸처럼 보일 수 있다.
예제로 보면 표현식 태그와
EL차이가 더 잘 보인다아래 예제는 같은
message파라미터를 세 가지 방식으로 출력하고 있다.
첫째는EL의 점 표기 방식이고, 둘째는EL의 대괄호 표기 방식이고, 셋째는 기존 표현식 태그 방식이다.
즉, 같은 값을 어떻게 더 간단하게 꺼낼 수 있는지를 비교하는 예제다.<!-- elexam2.jsp --> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>EL 테스트</title> </head> <body> <h2>EL의 Query 문자열 추출</h2> <hr> 전달된 메시지의 존재 여부 : ${ !empty param.message } <!-- 파라미터 존재 여부 확인 --> <hr> 전달된 메시지의 내용은 ${param.message} 입니다. <!-- 점 표기 방식 --> <br> 전달된 메시지의 내용은 ${param["message"]} 입니다. <!-- 대괄호 표기 방식 --> <br> 전달된 메시지의 내용은 <%=request.getParameter("message")%> 입니다. <!-- 기존 표현식 태그 방식 --> <br> </body> </html>이 예제의 핵심은 같은 요청 파라미터를 꺼내더라도
EL을 쓰면 훨씬 짧고 읽기 쉽게 표현할 수 있다는 점이다.
또 값이 없을 때와 있을 때의 차이도 같이 확인할 수 있다.
출력 결과는 아래와 같다.
이 이미지는
message파라미터를 전달하지 않은 상태에서 실행한 결과다.
그래서 전달된 메시지의 존재 여부는false로 나오고,EL과 표현식 태그 모두 값이 없는 상태를 그대로 보여 준다.
즉, 값이 없을 때는 보통 오류가 나는 것이 아니라, 존재 여부는 거짓으로 나오고 출력 자리는 비어 보일 수 있다고 이해하면 된다.
이 이미지는
message=안녕값을 함께 전달해서 실행한 결과다.
그래서 존재 여부는true가 되고,EL의 점 표기, 대괄호 표기, 표현식 태그가 모두 같은 값안녕을 출력하는 모습을 확인할 수 있다.
즉, 결과는 같지만 표현 방식은EL이 더 간단하다는 점이 분명하게 드러난다.
여기서 먼저 잡아야 할 핵심
이 구간에서 가장 중요하게 기억해야 할 것은 아래 내용이다.
EL은JSP에서 값을 더 짧고 쉽게 표현하기 위한 문법이다.- 표현식 태그와 비슷하게 값을 출력하지만,
EL이 더 간단하고 읽기 쉽다.EL은 값 추출뿐 아니라 산술, 비교,mod, 삼항 연산도 할 수 있다.- 요청 파라미터는
${param.message}처럼 간단하게 꺼낼 수 있다.empty를 사용하면 값 존재 여부도 바로 확인할 수 있다.
EL은 화면에 보여 줄 값을 표현식 태그보다 더 짧고 읽기 쉽게 꺼내기 위해 나온 문법이다.
이 기준이 잡혀야 다음에 나오는EL내장 객체와 저장 영역 설명도 훨씬 자연스럽게 이어진다.
4-2. EL 내장 객체
EL은 단순히 값만 출력하는 문법이 아니다.
어디에 저장된 값을 꺼낼지, 요청에서 어떤 값을 읽을지에 맞춰 바로 사용할 수 있는 전용 객체들도 함께 제공한다.
즉,EL내장 객체를 이해하면 값을 꺼낼 때 더 이상 긴Java코드를 직접 적지 않아도 된다.
특히 저장 영역에 들어 있는 값과 요청 파라미터를 구분해서 읽는 흐름이 훨씬 분명해진다.
EL내장 객체는 왜 필요한가앞에서 본
EL은${param.message}처럼 값을 짧게 꺼낼 수 있다는 점이 핵심이었다.
그런데 화면에서 꺼내는 값은 전부 같은 자리에 있는 것이 아니다.
어떤 값은 현재 페이지 안에 저장되어 있고, 어떤 값은 요청 객체 안에 있고, 어떤 값은 세션이나 애플리케이션 전체에 저장되어 있을 수 있다.
이때EL내장 객체를 쓰면 값이 어느 저장 영역에 있는지 바로 구분해서 꺼낼 수 있다.
또 요청 파라미터, 헤더, 쿠키처럼 웹 요청과 관련된 정보도 간단한 문법으로 읽을 수 있다.
- 저장 영역에 따라 값을 구분해서 꺼낼 수 있다.
- 요청 파라미터, 헤더, 쿠키 같은 정보도 쉽게 읽을 수 있다.
- 긴
Java코드 없이 화면에서 필요한 값을 바로 표현할 수 있다.즉,
EL내장 객체는 값을 짧게 출력하는 것에서 한 단계 더 나아가, 어디에 있는 값을 꺼내는지까지 분명하게 보여 주는 도구라고 보면 된다.
어떤
EL내장 객체를 먼저 알아야 하는가초보자 기준에서는 먼저 저장 영역 관련 객체부터 잡는 것이 좋다.
가장 기본은pageScope,requestScope,sessionScope,applicationScope다.
pageScope는 현재 페이지 범위에 저장된 값을 꺼낼 때 사용한다.
requestScope는 현재 요청 범위에서 값을 꺼낸다.
sessionScope는 같은 사용자 세션에 저장된 값을 꺼내고,applicationScope는 웹 애플리케이션 전체에서 공유하는 값을 꺼낸다.
그다음으로 자주 쓰는 것은param,paramValues,header,cookie다.
param은 요청 파라미터 하나를 읽을 때 쓰고,paramValues는 같은 이름으로 여러 값이 전달된 경우에 사용한다.
header는 요청 헤더를 읽고,cookie는 쿠키 값을 읽는다.
여기서도 이름만 보면 낯설 수 있다.
paramValues는 같은 이름으로 전달된 여러 값을 배열처럼 읽을 때 쓰는 객체이고,header는 브라우저가 함께 보낸 부가 정보를 읽는 객체다.
또cookie는 브라우저 쪽에 저장된 쿠키 값을 읽을 때 사용한다.
즉, 이 세 객체는 모두 요청과 함께 따라오는 추가 정보를 읽기 위한 도구라고 이해하면 쉽다.
pageScope는 현재 페이지 저장 영역을 본다.requestScope는 현재 요청 저장 영역을 본다.sessionScope는 사용자 세션 저장 영역을 본다.applicationScope는 애플리케이션 전체 저장 영역을 본다.param,paramValues,header,cookie는 요청과 관련된 값을 읽는다.즉,
EL내장 객체는 크게 보면 저장 영역을 읽는 객체와 요청 정보를 읽는 객체로 나누어 이해하면 쉽다.
EL은 자바 지역 변수를 바로 읽지 못한다이 구간에서 먼저 꼭 잡아야 할 점이 있다.
EL은 자바 지역 변수를 바로 읽는 문법이 아니다.
즉, 스크립트릿 태그 안에서 만든 지역 변수는 그대로는EL에서 보이지 않는다.
반대로pageContext.setAttribute()처럼 저장 영역에 값을 넣어 두면, 그때부터EL이 그 값을 읽을 수 있다.
이 차이를 알아야 왜 어떤 값은${name}으로 나오고, 어떤 값은 비어 있는지 헷갈리지 않는다.
- 자바 지역 변수는
EL이 바로 읽지 못한다.- 저장 영역에 넣은 값은
EL이 읽을 수 있다.- 즉,
EL은 변수 문법이라기보다 저장된 객체를 꺼내는 문법에 가깝다.즉,
EL은 자바 코드 안의 지역 변수를 직접 훑는 문법이 아니라, 저장 영역에 등록된 값을 꺼내는 문법이라고 이해해야 한다.
왜 지역 변수는 안 되고 저장 영역 값은 되는가
이 부분은 초보자가 많이 헷갈린다.
자바 지역 변수는 스크립트릿 코드가 실행되는 동안만 잠깐 존재하는 임시값에 가깝다.
즉, 자바 코드 블록 안에서 잠깐 쓰고 끝나는 값이라고 보면 된다.
반면 저장 영역에 넣은 값은pageContext,request,session,application같은 저장소에 등록된 값이다.
그래서EL은 이런 저장소를 기준으로 값을 찾을 수 있다.
즉, 지역 변수는 자바 코드 안의 임시값이고, 저장 영역 값은EL이 찾을 수 있도록 꺼내 놓은 값이라고 이해하면 훨씬 쉽다.
pageContext는 무엇인가여기서 자주 나오는
pageContext도 같이 잡아 두는 것이 좋다.
pageContext는 현재JSP페이지와 관련된 여러 정보를 다루는 내장 객체다.
즉, 지금 페이지 범위에서 값을 저장하거나 꺼낼 때 사용할 수 있는 기본 도구라고 보면 된다.
그래서pageContext.setAttribute("name", "자바")라고 쓰면, 현재 페이지 범위에name이라는 이름으로 값을 저장한다는 뜻이 된다.
이렇게 저장한 값은 같은 페이지 처리 흐름 안에서EL이 읽을 수 있다.
예제로 보면 지역 변수와 저장 영역 값 차이가 보인다
아래 예제는
EL이 자바 지역 변수는 바로 읽지 못하고,pageContext에 저장한 값부터 읽을 수 있다는 점을 보여 준다.
즉, 같은 이름name을 써도 어디에 들어 있느냐에 따라EL결과가 달라진다.<!-- elexam3.jsp --> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>EL 테스트</title> </head> <body> <h2>EL 변수</h2> <hr> name 변수의 값 : ${name} <!-- 아직 저장 영역에 없으므로 비어 있음 --> <br> <% String name = "듀크"; %> <!-- 자바 지역 변수 선언 --> name 변수의 값(표현식 태그) : <%= name %> <!-- 표현식 태그는 지역 변수 값을 출력할 수 있음 --> <br> name 변수의 값(EL) : ${name} <!-- EL은 자바 지역 변수를 바로 읽지 못함 --> <br> <% pageContext.setAttribute("name", "자바"); %> <!-- pageContext에 name 저장 --> name 변수의 값 : ${name} <!-- pageContext에 저장된 값 출력 --> <br> pageScope.name 변수의 값 : ${pageScope.name} <!-- pageScope를 명시해서 출력 --> <br> <hr> <% pageContext.setAttribute("number", 100); %> <!-- number 값을 pageContext에 저장 --> number 변수의 값 : ${number} <br> pageScope.number 변수의 값 : ${pageScope.number} <br> number 변수의 값에 23을 더한 값 : ${number + 23} <!-- 저장된 값을 EL 연산에 바로 사용 --> </body> </html>이 예제의 핵심은
EL이 자바 지역 변수name="듀크"는 바로 읽지 못하고,pageContext에 저장한 뒤부터${name}으로 읽을 수 있다는 점이다.
즉,EL은 코드 안의 지역 변수보다는 저장 영역에 등록된 값을 중심으로 동작한다.
출력 결과는 아래와 같다.
이 이미지를 보면 처음
${name}은 비어 있고, 표현식 태그로는듀크가 출력된다.
하지만pageContext.setAttribute("name", "자바")이후부터는${name}과${pageScope.name}이 모두자바를 출력한다.
즉,EL은 자바 지역 변수보다 저장 영역에 들어간 값을 읽는 문법이라는 점이 분명하게 드러난다.
저장 영역을 명시해서 꺼내면 더 정확해진다
EL에서는${msg}처럼 이름만 적어도 값을 찾을 수 있다.
하지만 값이 여러 저장 영역에 같은 이름으로 들어 있으면, 어떤 값을 꺼내는지 헷갈릴 수 있다.
이럴 때pageScope.msg,requestScope.msg,sessionScope.msg,applicationScope.msg처럼 저장 영역을 직접 적으면 더 정확하게 값을 구분할 수 있다.
- 같은 이름의 값이 여러 곳에 있을 수 있다.
- 저장 영역을 직접 적으면 어떤 값을 읽는지 분명해진다.
- 값 우선순위를 헷갈릴 때 특히 도움이 된다.
즉,
EL은 이름만으로도 값을 찾을 수 있지만, 저장 위치를 직접 쓰면 더 정확하고 읽기 쉬운 코드가 된다.
이름만 적으면 어떤 순서로 찾는가
이 부분도 같이 알고 있으면 결과를 훨씬 쉽게 해석할 수 있다.
EL에서${msg}처럼 이름만 적으면 보통pageScope→requestScope→sessionScope→applicationScope순서로 값을 찾는다.
즉, 범위가 더 좁은 저장 영역부터 먼저 확인한다고 생각하면 된다.
그래서 여러 저장 영역에 같은 이름의 값이 있다면, 가장 먼저 찾은 값이 화면에 나온다.
즉, 결과를 외우기보다 어느 순서로 탐색하는가를 이해하는 것이 더 중요하다.
예제로 보면 저장 영역별 값 차이가 한눈에 보인다
아래 예제는 같은 이름
msg를 네 저장 영역에 각각 넣은 뒤,EL로 꺼내는 코드다.
즉, 저장 위치가 달라도 같은 이름으로 값을 등록할 수 있고,EL내장 객체로 구분해서 읽을 수 있다는 점을 보여 준다.
여기서 사용하는session,application도JSP가 기본으로 제공하는 내장 객체다.
즉, 코드 안에서 따로 만든 변수가 아니라, 현재 세션과 애플리케이션 범위를 다룰 수 있도록JSP가 미리 준비해 둔 객체라고 이해하면 된다.<!-- elexam4.jsp --> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>EL 테스트</title> </head> <body> <h2>저장된 객체 추출</h2> <hr> <% pageContext.setAttribute("msg", "PageContext 객체에 저장된 객체"); request.setAttribute("msg", "HttpServletRequest 객체에 저장된 객체"); session.setAttribute("msg", "HttpSession 객체에 저장된 객체"); application.setAttribute("msg", "ServletContext 객체에 저장된 객체"); %> <!-- 같은 이름 msg를 각 저장 영역에 저장 --> pageScope 객체에서 추출 : ${pageScope.msg} <br> requestScope 객체에서 추출 : ${requestScope.msg} <br> sessionScope 객체에서 추출 : ${sessionScope.msg} <br> applicationScope 객체에서 추출 : ${applicationScope.msg} <br> <hr> msg 추출 : ${msg} <!-- 이름만 쓰면 우선순위에 따라 값이 결정됨 --> <br> </body> </html>이 예제의 핵심은 같은 이름
msg라도 저장 위치에 따라 서로 다른 값을 가질 수 있다는 점이다.
그리고pageScope,requestScope,sessionScope,applicationScope를 붙이면 그 차이를 정확하게 구분해서 읽을 수 있다.
출력 결과는 아래와 같다.
이 이미지를 보면 네 저장 영역에 같은 이름
msg를 넣었어도 각각 다른 값이 출력된다.
그리고 마지막${msg}는PageContext 객체에 저장된 객체를 출력하는데, 이는 이름만 적었을 때pageScope값이 가장 먼저 잡히기 때문이다.
즉, 이름만 써도 값을 찾을 수는 있지만, 저장 영역을 직접 적으면 훨씬 더 정확하게 읽을 수 있다는 점이 드러난다.
이전에 저장된 값이 남아 있는지 확인할 수도 있다
저장 영역은 모두 같은 수명으로 유지되지 않는다.
pageScope와requestScope는 짧게 유지되고,sessionScope와applicationScope는 더 오래 남을 수 있다.
그래서 어떤 페이지에서는 비어 있고, 어떤 저장 영역 값은 이전 실행 결과가 남아 있을 수도 있다.
아래 예제는 새로 값을 저장하지 않고, 현재 남아 있는 값을 그대로 꺼내는 코드다.
즉, 저장 영역마다 유지 범위가 다르다는 점을 결과로 확인하기 위한 예제라고 볼 수 있다.<!-- elexam4_1.jsp --> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>EL 테스트</title> </head> <body> <h2>저장된 객체 추출(4_1)</h2> <hr> pageScope 객체에서 추출 : ${pageScope.msg} <!-- 현재 페이지에 저장된 값 확인 --> <br> requestScope 객체에서 추출 : ${requestScope.msg} <!-- 현재 요청에 저장된 값 확인 --> <br> sessionScope 객체에서 추출 : ${sessionScope.msg} <!-- 세션에 남아 있는 값 확인 --> <br> applicationScope 객체에서 추출 : ${applicationScope.msg} <!-- 애플리케이션에 남아 있는 값 확인 --> <br> <hr> msg 추출 : ${msg} <!-- 이름만 적었을 때 어떤 저장 영역 값이 잡히는지 확인 --> <br> </body> </html>이 예제는 보통 바로 앞의
elexam4.jsp를 같은 브라우저 세션에서 실행한 뒤 보면 더 잘 이해된다.
그 상태에서는pageScope와requestScope값은 사라지고,sessionScope와applicationScope값은 남아 있어서${msg}도 그 우선순위에 따라 잡히는 모습을 볼 수 있다.
출력 결과는 아래와 같다.
이 이미지를 보면
pageScope와requestScope는 비어 있고,sessionScope와applicationScope값은 남아 있다.
또 마지막${msg}는HttpSession 객체에 저장된 객체를 출력하는데, 이는 현재 남아 있는 저장 영역 값들 중에서sessionScope값이 먼저 선택되기 때문이다.
즉, 저장 영역은 모두 같은 기간 유지되는 것이 아니라, 범위와 수명이 서로 다르다는 점을 이 결과로 확인할 수 있다.
여기서 먼저 잡아야 할 핵심
이 구간에서 가장 중요하게 기억해야 할 것은 아래 내용이다.
EL내장 객체는 저장 영역과 요청 정보를 더 쉽게 읽기 위한 객체다.pageScope,requestScope,sessionScope,applicationScope는 저장 영역을 구분해서 값을 꺼낼 때 사용한다.EL은 자바 지역 변수는 바로 읽지 못하고, 저장 영역에 넣은 값부터 읽을 수 있다.${msg}처럼 이름만 적으면 우선순위에 따라 값을 찾고,${pageScope.msg}처럼 쓰면 더 정확하게 값을 구분할 수 있다.- 저장 영역마다 유지 범위가 다르므로, 어떤 값은 바로 사라지고 어떤 값은 다음 요청에도 남을 수 있다.
EL내장 객체를 이해하면 값을 어디에서 꺼내는지, 왜 어떤 값은 보이고 어떤 값은 안 보이는지를 훨씬 정확하게 설명할 수 있다.
이 기준이 잡혀야 다음에 나오는EL의 점 연산자도 훨씬 자연스럽게 이해할 수 있다.
4-3. EL의 점 연산자
EL에서.연산자는 값을 꺼낼 때 아주 자주 쓰는 문법이다.
겉으로 보기에는 단순히객체.이름처럼 보이지만, 실제로는 대상이 무엇이냐에 따라 내부 동작 방식이 달라진다.
즉, 문법은 같아 보여도 항상 똑같은 방식으로 값을 찾는 것은 아니다.
그래서 이 구간에서는.연산자가 어떤 경우에 어떻게 해석되는지 정확히 구분해서 이해하는 것이 중요하다.
.연산자는 왜 중요한가앞에서
EL내장 객체를 보면서${param.message},${pageScope.msg}같은 문법을 이미 사용했다.
여기서param.message,pageScope.msg에 들어 있는.가 바로 점 연산자다.
이 문법이 중요한 이유는EL에서 값을 꺼내는 대부분의 표현이 이 형태로 쓰이기 때문이다.
즉,EL을 읽을 때는 결국 이.가 어떤 의미로 동작하는지를 아는 것이 핵심이다.
${param.message}처럼 요청 파라미터를 읽을 때도 사용한다.${pageScope.msg}처럼 저장 영역 값을 읽을 때도 사용한다.- 일반 객체의 속성 값을 읽을 때도 사용한다.
즉,
.연산자는EL에서 값을 꺼내는 가장 기본적인 문법이라고 보면 된다.
일반 객체에서는 어떻게 동작하는가
대상이 일반
Java객체라면,.연산자는 보통getter메서드를 호출하는 방식으로 동작한다.
예를 들어${user.name}이라고 쓰면, 내부적으로는user.getName()을 호출한 것처럼 해석된다.
여기서getter는 객체 안의 값을 꺼내기 위해 만든get이름()형태의 메서드다.
즉,name값을 꺼낼 때는getName()처럼 읽어 오는 메서드가 있다고 이해하면 된다.
즉, 객체 안에 있는 값을 직접 변수처럼 꺼내는 것이 아니라, 자바빈 규칙에 맞는getter를 통해 값을 읽는다고 이해하면 된다.
${user.name}→user.getName()처럼 동작한다.${product.price}→product.getPrice()처럼 동작한다.${cart.apple}→cart.getApple()처럼 동작한다.즉, 일반 객체에서의 점 연산자는 속성 이름을 보고 알맞은
getter를 찾는 방식으로 생각하면 된다.
Map에서는 어떻게 동작하는가대상이
Map객체라면 동작 방식이 달라진다.
이때${map.key}는getter를 찾는 것이 아니라, 내부적으로map.get("key")처럼 동작한다.
여기서Map은 이름표 역할을 하는key와 실제 값인value를 짝으로 저장해 두는 자료구조다.
쉽게 말하면 “이름표를 주면 거기에 연결된 값을 꺼내는 보관함” 정도로 이해하면 된다.
즉, 일반 객체와 똑같이 점을 찍었더라도, 실제로는 메서드 호출 기준이 아니라 키 값을 이용한 조회 기준으로 해석된다.
이 점이 처음에는 가장 헷갈리기 쉽다.
${param.message}→param.get("message")처럼 동작한다.${pageScope.msg}→pageScope.get("msg")처럼 동작한다.${header.host}→header.get("host")처럼 동작한다.즉,
Map에서는 점 연산자가 키를 이용해서 값을 찾는 문법으로 동작한다.
왜 같은 문법인데 다르게 동작하는가
겉으로는
${something.name}처럼 전부 같은 모양이라서, 처음 보면 전부 같은 방식으로 찾는다고 생각하기 쉽다.
하지만EL은 대상이 일반 객체인지,Map인지에 따라 더 자연스러운 방식으로 값을 찾도록 만들어져 있다.
그래서 문법은 단순하지만, 내부적으로는 대상 타입에 맞춰 해석 방식이 달라진다.
이 차이를 알아야${user.name}과${param.name}이 왜 같은 모양인데 다른 기준으로 동작하는지 이해할 수 있다.
- 일반 객체면
getter기준으로 찾는다.Map이면get("key")기준으로 찾는다.- 문법은 같아도 내부 해석 방식은 다르다.
즉,
EL의 점 연산자는 단순한 문자 기호가 아니라, 대상에 따라 적절한 방식으로 값을 찾도록 해 주는 공통 문법이다.
왜 굳이 같은
.문법을 쓰는가이 부분도 같이 이해하면 훨씬 덜 헷갈린다.
EL은 사용하는 사람이 매번 문법을 바꾸지 않아도 되게 하려고, 값을 꺼낼 때 기본 모양을 비슷하게 유지한다.
즉, 보는 사람 입장에서는${무언가.이름}처럼 통일된 형태로 읽을 수 있게 만든 것이다.
대신 내부에서는 대상이 일반 객체인지,Map인지에 따라 알맞은 방식으로 해석한다.
즉, 바깥 문법은 단순하게 통일하고, 안쪽 동작은 대상에 맞게 달라진다고 이해하면 쉽다.
예시로 보면 차이가 더 분명해진다
아래처럼 같은
.문법을 써도 대상이 무엇인지에 따라 의미가 달라진다.<!-- EL 점 연산자 예시 --> ${user.name} <!-- 일반 객체면 user.getName()처럼 동작 --> ${param.message} <!-- param은 Map처럼 동작하므로 param.get("message")처럼 해석 --> ${pageScope.msg} <!-- pageScope도 Map처럼 동작하므로 pageScope.get("msg")처럼 해석 -->이 예시에서
user.name은 일반 객체 속성을 읽는 표현이고,param.message,pageScope.msg는 키 이름으로 값을 찾는 표현이다.
즉, 눈에 보이는 문법은 같지만, 내부 기준은 서로 다르다.
대괄호 문법과도 연결된다
이 점을 이해하면 왜
EL에서 대괄호 문법도 함께 쓰는지 자연스럽게 보인다.
예를 들어${param.message}와${param["message"]}는 같은 값을 가리킨다.
즉, 점 연산자로 키를 읽을 수도 있고, 대괄호로 키를 직접 적을 수도 있다.
특히 키 이름이 복잡하거나, 점 표기로 쓰기 애매한 경우에는 대괄호 문법이 더 분명하게 보일 수 있다.
${param.message}는 점 표기 방식이다.${param["message"]}는 대괄호 표기 방식이다.- 둘 다 같은 값을 가리킬 수 있다.
즉, 점 연산자를 이해하면 대괄호 문법도 함께 이해하기 쉬워진다.
여기서 먼저 잡아야 할 핵심
이 구간에서 가장 중요하게 기억해야 할 것은 아래 내용이다.
EL의 점 연산자는 값을 꺼낼 때 가장 자주 쓰는 문법이다.- 일반 객체에서는
getter를 호출하는 것처럼 동작한다.Map에서는get("key")를 호출하는 것처럼 동작한다.${param.message}와${pageScope.msg}는Map방식으로 해석된다.- 문법은 같아도 대상에 따라 내부 동작 방식이 달라진다.
EL의 점 연산자는 같은 모양이라도, 대상이 일반 객체인지Map인지에 따라 해석 방식이 달라진다.
이 기준이 잡혀야 마지막Filter구간으로 넘어갈 때도EL관련 개념이 깔끔하게 정리된다.
4-4. Filter
Filter는 웹 자원에 요청이 도착하기 전이나, 자원 처리가 끝난 뒤에 공통 작업을 가로채서 처리하는 기능이다.
즉,Servlet이나JSP가 직접 실행되기 전과 후에 끼어들어서 필요한 공통 처리를 먼저 하거나 나중에 할 수 있게 해 주는 구조다.
이 개념이 중요한 이유는, 여러 페이지에서 똑같은 코드를 반복해서 넣지 않아도 되기 때문이다.
로그인 검사, 요청 로그 기록, 인코딩 처리, 압축, 암호화처럼 여러 화면에서 공통으로 필요한 작업은 각 페이지에 따로 넣기보다Filter로 한곳에 모아 두는 편이 훨씬 깔끔하다.
왜
Filter가 필요한가웹 프로그램을 만들다 보면 어떤 처리들은 특정 페이지 하나에만 필요한 것이 아니라, 여러 자원에 공통으로 필요해진다.
예를 들어 로그인하지 않은 사용자가 관리자 페이지로 들어오지 못하게 막는 작업, 요청이 들어올 때마다 로그를 남기는 작업, 한글 인코딩을 맞추는 작업이 대표적이다.
이런 코드를 모든Servlet과JSP안에 직접 넣으면 중복도 많아지고, 나중에 수정할 때도 여러 파일을 다 고쳐야 한다.
그래서 공통 작업을 한곳에 모아 두고, 요청이 들어올 때마다 먼저 거치게 하는 구조가 필요하다.
- 여러 자원에서 같은 코드를 반복하지 않아도 된다.
- 공통 기능을 한곳에서 관리할 수 있다.
- 수정이 필요할 때 여러 파일을 동시에 고치지 않아도 된다.
즉,
Filter는 공통 처리를 분리해서 코드 중복을 줄이고 관리하기 쉽게 만들기 위해 필요하다.
Filter는 언제 동작하는가
Filter는 요청이 실제 자원에 도달하기 전에도 동작할 수 있고, 자원 처리가 끝난 뒤에도 동작할 수 있다.
즉, 요청 앞단과 뒷단에서 모두 관여할 수 있다.
예를 들어 요청이 들어오자마자 인증 여부를 검사하고, 조건에 맞지 않으면 자원까지 가지 못하게 막을 수 있다.
반대로 자원이 응답을 만든 뒤에는 응답 내용을 가공하거나 로그를 남길 수도 있다.
- 자원 실행 전에 먼저 검사하거나 준비할 수 있다.
- 자원 실행 후에 결과를 정리하거나 기록할 수 있다.
- 즉, 전처리와 후처리를 모두 맡을 수 있다.
그래서
Filter는 단순히 요청을 통과시키는 장치가 아니라, 요청과 응답 흐름의 앞뒤를 관리하는 공통 처리 장치라고 보면 된다.
요청 흐름으로 보면 더 쉽다
Filter는 실제 요청 흐름 안에서 보면 더 이해가 쉽다.
브라우저가 요청을 보낸 뒤, 그 요청이 바로Servlet이나JSP로 가는 것이 아니라 먼저Filter를 통과할 수 있다.
이때Filter는 요청을 검사해서 “통과시킬지”, “여기서 끝낼지”를 결정할 수 있다.
조건에 맞으면 원래 자원으로 넘기고, 조건에 맞지 않으면 자원까지 가지 못하게 막을 수도 있다.
즉, 문 앞에서 먼저 확인하고 들여보내는 구조라고 생각하면 쉽다.
그리고 자원 실행이 끝난 뒤에는 다시 돌아오는 응답에 대해 후처리를 할 수도 있다.
그래서Filter는 앞에서 한 번, 뒤에서 한 번 관여할 수 있는 구조라고 이해하면 된다.
어떤 작업에 잘 어울리는가
Filter는 공통으로 반복되는 작업에 특히 잘 어울린다.
대표적으로 인증, 로깅, 인코딩 처리, 데이터 압축, 암호화 같은 작업이 여기에 해당한다.
예를 들어 로그인 여부를 검사해야 하는 페이지가 여러 개라면, 각 페이지에서 일일이 검사하기보다Filter에서 먼저 검사하고 통과한 요청만 자원으로 보내는 구조가 훨씬 자연스럽다.
또 요청이 들어올 때마다 접속 기록을 남기고 싶다면, 각 페이지에 로그 코드를 넣는 대신Filter에서 한 번에 처리할 수 있다.
- 인증 검사
- 요청 로그 기록
- 인코딩 설정
- 응답 압축
- 데이터 암호화
즉,
Filter는 특정 화면의 본문 로직보다, 여러 자원에 공통으로 적용해야 하는 작업에 더 잘 어울린다.
본문 코드와 무엇이 다른가
Servlet이나JSP본문 안에 직접 코드를 넣으면, 그 코드는 해당 자원 안에서만 동작한다.
반면Filter는 자원 바깥에서 먼저 요청을 받아 공통 처리를 하고, 필요한 경우에만 원래 자원으로 넘긴다.
즉, 본문 코드는 그 페이지 자체의 역할을 수행하는 데 집중하고,Filter는 그 앞뒤에서 공통 정책을 적용하는 데 집중한다.
이 차이를 알아야Filter를 왜 따로 두는지 이해가 된다.
Servlet과JSP본문은 개별 자원 역할에 집중한다.Filter는 여러 자원에 공통으로 적용되는 정책을 맡는다.- 그래서 둘은 경쟁 관계가 아니라 역할 분리 관계에 가깝다.
즉,
Filter는 본문 로직을 대신하는 장치가 아니라, 본문에 들어가기 전후의 공통 처리를 따로 맡는 장치다.
무엇은 본문 로직이고 무엇은
Filter에 어울리는가이 기준도 같이 잡아 두면 훨씬 덜 헷갈린다.
예를 들어 회원 가입 데이터를 실제로 저장하는 일, 게시글을 조회해서 결과를 만드는 일, 주문 금액을 계산하는 일은 해당Servlet이나 서비스 로직의 역할에 가깝다.
즉, 그 페이지나 기능 자체가 해야 하는 본문 일이다.
반면 로그인 여부 검사, 요청 로그 기록, 한글 인코딩 설정처럼 여러 자원에서 반복해서 필요한 일은Filter쪽이 더 어울린다.
즉, 개별 기능의 내용 자체가 아니라, 여러 기능에 공통으로 걸리는 규칙과 준비 작업이Filter몫이라고 이해하면 된다.
처리 흐름은 어떻게 이해하면 쉬운가
Filter의 흐름은 간단하게 보면 이렇다.
요청이 들어오면 먼저Filter가 요청을 받는다.
그다음 필요한 전처리를 하고, 원래 자원으로 요청을 넘긴다.
자원이 응답을 만들고 나면, 다시Filter가 후처리를 할 수 있다.
- 요청 도착
Filter에서 전처리- 원래
Servlet또는JSP실행Filter에서 후처리- 최종 응답 전송
즉,
Filter는 요청과 응답 흐름의 중간에 끼어들어 공통 작업을 수행하는 구조라고 이해하면 된다.
여기서 먼저 잡아야 할 핵심
이 구간에서 가장 중요하게 기억해야 할 것은 아래 내용이다.
Filter는 요청이 자원에 도달하기 전이나 후에 공통 처리를 하는 구조다.- 인증, 로깅, 인코딩 처리, 압축, 암호화 같은 작업에 잘 어울린다.
- 여러 자원에 반복되는 코드를 본문 안에 직접 넣지 않고 따로 분리할 수 있다.
Filter는 개별 자원 로직을 대신하는 것이 아니라, 공통 정책을 앞뒤에서 적용하는 장치다.
Filter의 핵심은 공통 전처리와 후처리를 한곳에 모아서, 여러Servlet과JSP에 반복해서 넣지 않게 만드는 데 있다.
이 기준이 잡히면JSP와Servlet을 어디까지 자원 본문에서 처리하고, 어디부터 공통 처리로 분리해야 하는지도 자연스럽게 보이기 시작한다.