EL(Expression language)은 JSTL(JavaServer Pages Standard Tag Library) 1.0에서 처음 도입되었으며, 그 취지는 개발자들이 빈, 어레이, 요청 변수, 쿠키 등에 저장된 애플리케이션 데이터를 JSP 페이지에서 쉽게 액세스할 수 있도록 하는 것이었다. EL은 JSP 저작(Authoring)을 간소화하는 데 매우 유용한 것으로 입증되었는데, 가령 다음과 같이 간단한 행을 이용하여 빈에 저장된 데이터를 JSP 페이지에서 액세스할 수 있었다.
You have ${cart.itemCount} items in your cart.EL 표현식 ${cart.itemCount}는 cart 변수가 참조하는 빈에 저장된 itemCount 속성의 값을 액세스한다.
EL은 이러한 유용성으로 인해 JSP 2.0에 통합되었고, 이어서 JavaServer Faces 1.0이 EL을 채택하기에 이른다. 그 이후로도 EL은 더욱 확장되어 빈 속성을 설정하고 액션을 위한 메소드를 지정할 수 있게 되었다. JSP 2.1에서는 통합 EL이 새로 소개되었는데, 이는 결국 JSP 2.0과 JavaServer Faces 1.0의 EL을 통합한 것이라고 보면 된다.
본 팁에서는 JSP 2.1에 소개된 통합 EL의 여러 가지 새로운 기능들을 다루어 보도록 하겠으며, 또한 여기에는 예제 웹 애플리케이션 패키지가 첨부되어 있다. 팁의 코드 예제들은 패키지에 포함되어 있는 예제 소스 코드에서 발췌한 것이다.
ELContext
JSP 2.1에서 EL과 관련하여 새로 포함된 것 중 하나가 바로 ELContext 클래스인데, 이 클래스는 JSP엔진이 EL 표현식을 파싱하고 평가하는 데 사용하는 컨텍스트 정보를 제공한다. JSP 컨테이너는 JSP 페이지를 위한 기본값 ELContext를 제공하고, 마찬가지로 JSF 서블릿은 JavaServer Faces 기술을 사용하는 애플리케이션에서 EL 표현식 평가를 위한 기본값 ELContext를 제공한다.
ELContext 오브젝트에는 EL 표현식의 파싱과 평가에 사용되는 3개의 오브젝트에 대한 레퍼런스가 포함되어 있다.
FunctionMapper: EL 표현식을 파싱할 때 EL 함수를 분해하는 데 사용된다.VariableMapper: EL 표현식을 파싱할 때 EL 변수를 분해하는 데 사용된다.ELResolver: EL 표현식을 평가할 때 오브젝트와 그 속성을 분해하는 데 사용된다.
ELContext의 흥미로운 부분 중 하나가 바로 플러거블(pluggable)하다는 점이다. JSP API를 이용하면 JSP 페이지에서 커스텀 ELContext를 사용할 수 있다. JSP 컨테이너가 제공하는 ELContext에는 FunctionMapper, VariableMapper, ELResolver를 위한 기본값이 포함되어 있는데, 보통의 경우에는 이를 교체할 필요가 없지만 사용자가 이 참조 오브젝트들을 교체할 수는 있다. 이제 커스텀 버전의 ELResolver를 사용하는 방법에 관해 알아보기로 하자.
커스텀 ELResolver 사용하기
2006년 8월 26일자 테크 팁 커스텀 ELResolver를 이용하여 Java EE 통합 EL 확장하기에서는 JavaServer Faces에서 커스텀 ELResolver를 생성하고 사용하는 방법에 대해 살펴보았다. 이번에는 JSP에서 커스텀 ELResolver를 사용하는 예제를 살펴보기로 하자. 예제는 원래 JSP 2.1의 공동 스펙 리드였던 Mark Roth가 작성한 것으로, 이 예제에서 커스텀 ELResolver는 다음과 같은 다양한 포맷을 이용하여 화면 색상을 EL 표현식으로 지정할 수 있게 해준다.
- 이름 기준:
${Color.MidnightBlue} - RGB 값 기준:
${Color[100][250][200]} - Hex String 기준:
${ "#191970"}
예제를 실행하면 JSP 페이지는 아래와 같은 내용을 디스플레이한다.
ELResolver를 살펴보기에 앞서 이를 JSP 컨텍스트에 플러그하는 방법을 알아보기로 하자. 이 작업은 ELResolverListener 클래스에서 이루어진다. 본 팁에 첨부된 예제 애플리케이션 패키지를 열면 ELResolverListener와 본 팁의 다른 코드 예제를 위한 소스 코드를 볼 수 있다.
public class ELResolverListener implements ServletContextListener { public void contextInitialized(ServletContextEvent evt) { ServletContext context = evt.getServletContext(); JspApplicationContext jspContext = JspFactory.getDefaultFactory().getJspApplicationContext(context); jspContext.addELResolver(new ColorELResolver()); } }JSP 컨텍스트에 커스텀 ELResolver를 플러그하기 위해 먼저 JspFactory에서 JspApplicationContext를 획득해야 한다는 점에 유의할 것. 그런 다음 JspApplicationContext 메소드 addELResolver를 호출하고 커스텀 ELResolver의 인스턴스를 건네준다. 단, 이 작업은 반드시 애플리케이션이 어떠한 요청도 받기 전에 수행되어야 한다.
컨테이너의 기본값 ELResolver는 실제로는 다음과 같은 순서의 자식 ELResolver들의 리스트로 구성된 CompositeELResolver이다.
ImplicitObjectELResolverCustomELResolversMapELResolverListELResolverArrayELResolverBeanELResolverScopedAttributeELResolver
속성 분해 과정에서 resolver의 조회는 목록의 해당 순서에 따라 이루어지며, ImplicitObjectELResolver는 항상 먼저 호출되므로 오버라이드될 수 없다. 그러나, 가령 BeanELResolver 또는 ScopedAttributeELResolver와 같이 순서가 낮은 ELResolver의 경우에는 오버라이드가 가능하다.
다음은 커스텀 ELResolver인 ColorELResolver 내 코드의 일부이다. 이 코드는 ${Color.blue}와 같은 표현식을 처리하며, ColorELResolver를 위한 전체 코드는 추가의 표현식 포맷을 처리한다.
public class ColorELResolver extends ELResolver { public Object getValue(ELContext context, Object base, Object property) throws ELException { Object result = null; if(base == null) { // Resolving first variable (e.g. ${Color}). String propertyName = (String)property; if(propertyName.equals("Color")) { result = new ColorImplicitObject(); context.setPropertyResolved(true); } } else if(base instanceof ColorImplicitObject) { // Resolving a property on ${Color} ColorImplicitObject color = (ColorImplicitObject)base; result = ColorImplicitObject.fromName(colorName); context.setPropertyResolved(true); } return result; } }커스텀 ELResolver는 반드시 javax.el.ELResolver를 확장하고 모든 해당 메소드에 대한 구현을 제공해야 한다. 이 예제에서 주안점은 EL 표현식을 즉시 평가하는 것이므로, 커스텀 ELResolver인 ColorELResolver는 단지 getValue만을 구현하고 다른 메소드에 대한 기본적인(skeletal) 구현을 제공한다.
getValue 메소드는 ELContext, 베이스, 속성의 세 가지 인수를 취한다. ColorELResolver는 반드시 베이스와 속성의 특수 조합을 처리해야 하는데, 단 ${Color.blue}와 같은 EL 표현식을 평가하려면 ELResolvers에 2회의 트립이 필요하다는 점에 유의할 것. 첫 번째 트립은 Color를 분해하며, 이 경우 베이스는 null이고 속성은 Color이다. 여기서 ColorELResolver는 getValue 메소드를 위한 ColorImplicitObject의 인스턴스를 리턴한다.
두 번째 트립에서는 ELResolvers가 재조회되지만 이번에는 베이스는 ColorImplicitObject이고 속성은 문자열 "blue"이다. 여기서 리턴된 결과는 청색을 나타내는 ColorRGB의 인스턴스이다. ColorImplicitObject 클래스에는 색상 이름을 색상 값과 매칭시키는 테이블과 색상 이름을 색상 값으로 변환하는 fromName 메소드가 포함된다.
context.setPropertyresolved(true); 문장은 이 ELResolver에 의해 쌍(베이스, 속성)이 성공적으로 분해되었음을 나타내며, 동시에 이는 다른 ELResolver에서 추가 분해를 수행할 필요가 없음을 의미하기도 한다.
ColorELResolver를 위한 실제 코드는 위에 표시된 것보다 더 복잡한데, 그 이유는 ColorImplicitObject를 위해 반드시 분해되어야 하는 다른 속성을 처리해야 하기 때문이다. 하지만 기본적인 동작은 설명한 내용과 동일하다.
두 번째 예제
이제 커스텀 ELResolver를 사용하는 두 번째 예제를 살펴보기로 하자. 이 예제에서 커스텀 ELResolver는 빈 메소드의 호출이 가능하도록 EL을 확장한다. EL 함수는 static 메소드로 제한되며, MethodExpression, 즉 오브젝트 상에서 메소드를 참조하는 표현식은 JSP에서 호출될 수 없다. 따라서, JSP 페이지의 오브젝트 상에서 빈 메소드와 같은 메소드를 호출하고자 한다면 이 예제에 포함된 것과 같은 기법을 사용해야 한다. 이 방식은 다소 어색한 문법을 초래하긴 하지만 EL의 확장 가능성을 보여준다는 점에서 주목할 필요가 있다.
목표는 다음과 같은 EL 표현식을 가능케 하는 것이다.
${myBean.meth["arg1"]["arg2"]} 여기서 EL 표현식은 myBean 빈의 meth 메소드를 호출하고, 메소드에 2개의 인수, arg1과 arg2를 건네준다.
통상적으로 이 표현식을 처리할 때 BeanResolver는 myBean의 getter 메소드 getMethod를 호출하게 된다. 하지만, 커스텀 ELResolver의 경우 BeanELResolver에 앞서 조회가 이루어지기 때문에 커스텀 ELResolver를 이용하여 메소드의 호출을 삽입할 수 있다(물론, 해당 메소드가 존재할 경우). 커스텀 ELResolver를 이용하면 getter 메소드뿐 아니라 빈 내의 어떠한 메소드도 호출이 가능하다.
다음은 customELResolver인 MethodInvocationELResolver의 일부이다.
public class MethodInvocationELResolver extends ELResolver { public Object getValue(ELContext context, Object base, Object property) throws ELException { ... ResolvedMethod meth; if (base instanceof ResolvedMethod) { meth = (ResolvedMethod) base; meth.add(property); context.setPropertyResolved(true); return meth.isReady()? meth.invoke(): meth; } meth = getMethod(base, properties); if (meth != null) { context.setPropertyResolved(true); result = meth.isReady()? meth.invoke(): meth; return result; } return null; } private ResolvedMethod getMethod( Object base, Object property) { String methodName = property.toString(); Class beanClass = base.getClass(); for (Method method: beanClass.getMethods()) { if (method.getName().equals(methodName)) { return new ResolvedMethod(method); } } return null; } private static class ResolvedMethod { Method method; int argumentCount; ArrayList<Object> args = new ArrayList<Object>(); public ResolvedMethod(Method method) { this.method = method; argumentCount = method.getParameterTypes().length; } ...언제나처럼 customELResolver는 ELResolver를 확장하고, getValue를 오버라이드한다. MethodInvocationELResolver는 String일 것으로 예상되는 인수 속성이 인수 베이스로 건네지는 빈 내의 메소드인지 여부를 확인하는데, 그런 상황에서 메소드가 인수를 포함하고 있지 않다면 즉시 호출이 이루어진다. 반대로, 메소드가 인수를 가지는 경우에는 MethodInvocationELResolver는 메소드 정보를 저장한다. 다시 말해서, 쌍(myBean, method)을 분해할 때 customELresolver가 인수를 만나지 않았기 때문에 반드시 메소드 정보를 저장해야 하는 것이다. MethodInvocationELResolver는 모든 인수를 받은 후에만 메소드를 호출할 수 있다.
가령 ${myBean.meth["arg1"]["arg2"]}와 같은 2개의 인수로 EL 표현식을 평가하려면 MethodInvocationELResolver에 대해 4회의 트립이 필요하고, 각 트립에는 getValue 메소드 내에 서로 다른 쌍(베이스, 속성)의 조합이 포함된다. customELResolver는 이들을 모두 분해해야 한다.
customELResolver를 테스트하기 위해 예제에는 JSP 페이지, invoke.jsp가 포함되어 있다.
<jsp:useBean id="map" class="java.util.HashMap"/> Adding ("key1", "item1"): ${map.put["key1"]["item1"]} </br> Adding ("key2", "item2"): ${map.put["key2"]["item2"]} </br> map is ${map}<br/>이 페이지는 HashMap 빈의 put 메소드를 두 차례에 걸쳐 호출하는데, 메소드를 호출할 때마다 2개의 인수를 건네준 다음 HashMap의 값을 프린트한다.
예제를 실행하면 JSP 페이지는 아래와 같은 내용을 디스플레이한다.
통합 EL을 비롯한 JSP 2.1의 새로운 기능에 관한 자세한 내용을 보려면 Web Tier to Go With Java EE 5 : Summary of New Features in JSP 2.1 Technology를 참조할 것.
예제 실행하기
예컨대 Java EE 5 SDK의 애플리케이션 서버처럼 Java EE 5를 준수하는 어떠한 애플리케이션 서버에서도 예제 애플리케이션 실행이 가능하다. Java EE 5 SDK는 Java EE 다운로드 페이지에서 다운로드할 수 있다.
- 본 팁에 첨부된 예제 애플리케이션을 다운로드하여 압축을 풀면
ttjan2007el.war파일이 나타난다.
ttjan2007el.war파일을<install_dir>/domains/domain1/autodeploy에 복사한다. 여기서<install_dir>은 애플리케이션 서버가 설치된 디렉토리를 나타낸다.
- 애플리케이션 서버를 시작한다.
- 첫 번째 예제를 실행하려면 브라우저에서 아래의 URL을 연다.
http://localhost:8080/ttjan2007el/elcolor.jsp
- 두 번째 예제를 실행하려면 브라우저에서 아래의 URL을 연다.
http://locathost:8080/ttjan2007el/invoke.jsp
글쓴이 소개
Kin-man Chung은 썬 마이크로시스템즈의 수석 스태프 엔지니어로, C++ SPARC 컴파일러를 위한 코드 생성 및 최적화 작업에 참여한 바 있다. 최근 들어 Tomcat과 GlassFish 등의 웹 컨테이너 작업에도 참여했던 그는 현재 JavaServer Pages 2.1의 공동 스펙 리드로 있다.
"Java EE" 카테고리의 다른 글
- Attach API (댓글 18개 / 트랙백 1개) 2007/09/03
- JSP 2.0 EXPRESSION LANGUAGE (댓글 1개 / 트랙백 0개) 2004/02/05
- 환경 엔트리를 이용해서 배포의 사용자 정의하기 (댓글 2개 / 트랙백 0개) 2003/12/24
- JAX-WS를 이용한 웹 서비스 개발 (댓글 1개 / 트랙백 0개) 2006/01/18
- EJB 2.1로 메시지 구동 빈 이용하기 (댓글 1개 / 트랙백 0개) 2005/05/18
- EclipseLink를 사용하여 JPA에서 반복 불가능한 읽기 방지 (댓글 0개 / 트랙백 1개) 2008/07/09
- 컴포넌트 시스템과 클래스 로더 경계 (댓글 1개 / 트랙백 0개) 2004/10/05
- JAX-WS Dispatch 및 Provider API를 이용한 문서 처리 (댓글 4개 / 트랙백 0개) 2006/09/15
- POJO를 Persistent Entity로 변환하기 (댓글 1개 / 트랙백 0개) 2005/12/27
- SAAJ 소개 (댓글 1개 / 트랙백 0개) 2005/06/08
댓글을 달아 주세요
자바 세계는 끝없이 펼쳐져있네요 .. 감사합니다..
2007/09/07 20:20좋은 정보 감사합니다!
2007/09/15 21:48좋은 정보 감사해요~
2007/09/19 03:57자바는 알면 알수록 재미가 있어지네요. 좋은 자료 잘 활용하겠습니다.
2007/09/19 15:42좋은 정보 많이 얻어가요~
2007/09/19 23:19