홈페이지 꼭대기로

JSP 2.0: 뭐가 바뀌었나? - 1부

원문 : http://www.onjava.com/pub/a/onjava/2003/11/05/jsp.html

by Hans Bergsten, JavaServer Pages, 3판의 저자

번역 손권남(kwon37xi_aT_yahoo dOt co DoT kr)

2004/07/06

기다림이 거의 끝나간다(번역시점에서는 이미 끝났다). 최신 JavaServer Pages(JSP)스펙인 JSP 2.0이 다른 J2EE 1.4스펙과 함께 발표될 때가 가까워왔다. 소수점 앞부분의 버전 번호가 바뀐것으로 보아 JSP를 사용하는 많은 방식들이 바뀌었음을 알 수 있다. 더이상 JSP페이지에 JAVA 코드는 필요없다. 새로운 표현식(EL:Expression Language)과 JSP Standard Tag Library(JSTL)덕분이다. 또한 커스텀 액션을 개발하는 새로운 방식덕분에 코드 재사용이 훨씬 쉬워졌다. 보다 명확하게 말해, JSP 2.0은 다음과 같은 이점들을 가지고 있다.

이 기사는 JSP 2.0 의 새로운 기능을 설명하는 기사 시리즈의 첫번째 이다. 이번 장에서는 EL과 관련된 사항만 볼 것이며, 다른 사항들은 다음 회에 보기로 한다. 나는 당신이 JSP 1.2에 익숙하고 JSP Standard Tag Library(JSTL)에 대해 들어보기는 했다고 가정하고 글을 썼다.

당신은 또한 나의 JavaServer Pages 책 3판에 관심이 있을 지도 모른다. 이 책은 이제 설명할 모든 것을 더 자세히 설명하고, 당신이 JSP와 JSTL을 모를 수도 있다고 생각하고 쓴 것이다. 이 책은 2003년 12월에 출간될 예정이다. 하지만 Amazon.com 등의 서점에서 미리 주문할 수도 있다.

Expression Language (EL)

당신이 JSTL을 사용해봤다면, 이미 JSP Expression Lnaguage를 잘 알고 있을 것이다. EL은 JSTL 1.0 스펙에서 소개된 것으로 JAVA코드를 대신해 실행시간에 액션(태그) 엘리먼트의 속성에 값을 지정하는 역할을 한다. JSTL의 EL은 매우 빨리 인기를 끌었다. 하지만 문제가 하나 있는데 JSTL EL은 오직 JSTL 액션에서만 작동했고, 커스텀 액션은 표준이 아닌 API를 사용해서 EL을 수행하도록 특별히 작성되어야만 EL을 사용할 수 있었다.

JSP 2.0을 시작하며, JSP 컨테이너 자신이 EL 표현식을 해석할 수 있게 되었고, 표준과 커스텀 액션 그리고, 템플릿 텍스트(HTML같은)와 같이 JAVA 코드를 사용해야 했던 모든 곳에서 EL을 사용할 수 있게 되었다.

구체적인 예제를 보기전에, EL이 뭔지 더 자세히 살펴보자. EL은 JavaScript에서 조금 확장하고 XPath(XML 문서의 정보에 접근하기 위해 사용되는 언어)에서 힌트를 얻어 만들어진 언어이다. 그러나 EL은 값이 없는 변수(null)에 대해 좀더 관대하고, 데이타형 변환을 좀더 자동적으로 해준다. 이러한 특징들은 파라미터들을 폼에서 얻어오는 웹 어플리케이션에 중요하다. 파라미터들이 몇몇 요청에서는 존재하고 어떤 경우에는 값이 없을 수 있으며, 브라우저가 파라미터값을 항상 문자열(text)로 보내는데 반해, 웹 어플리케이션에서는 숫자나 불린(true/false)로 사용해야 할 경우도 생긴다. EL이 설계된 방식에 따르면, 값이 없을 경우(null)나, 형 변환 같은 것에 전혀 신경쓸 필요가 없다.

EL 표현식은 변수와 연산자를 포함한다. JSP 스코프(page, request, session, application)에 저장된 어떤 빈이라도 EL 변수로서 사용될 수 있다. 게다가 EL은 다음과 같은 내장 변수들도 지원한다:

변수 이름설명
pageScope 모든 page 스코프 변수에 대한 컬렉션(java.util.Map).
requestScope 모든 request 스코프 변수에 대한 컬렉션(java.util.Map).
sessionScope 모든 session 스코프 변수에 대한 컬렉션( java.util.Map).
applicationScope 모든 application 스코프 변수에 대한 컬렉션(java.util.Map)
param 모든 request 파라미터 값들을 한개의 문자열로 가진 컬렉션(java.util.Map).
paramValues 모든 request 파라미터의 값들을 파라미터당 문자열 배열로 가진 컬렉션(java.util.Map).
header 모든 헤더의 값들을 헤더당 하나의 문자열로 가진 컬렉션(java.util.Map).
headerValues 모든 헤더의 값들을 헤더당 문자열 배열로 가진 컬렉션(java.util.Map).
cookie 모든 쿠키의 값들을 쿠키당 한개의 javax.servlet.http.Cookie의 값으로 가진 컬렉션(java.util.Map).
initParam 모든 어플리케이션의 초기화 파라미터를 파라미터당 한개의 문자열로 가진 컬렉션(java.util.Map).
pageContext javax.servlet.jsp.PageContext 클래스로서, 다양한 request 데이타에 대한 액세스를 가능케 한다.

연산자들은 변수들로 뭘 할 것인지를 지정하는 것이다. EL에서 사용할 수 있는 연산자들은 프로그래밍을 해봤다면 많이 눈에 익을 것이다. 왜냐면, 거의 모든 언어에서 지원되는 연산자들과 같기 때문이다.

연산자설명
. 빈의 프라퍼티나 Map의 엔트리 접근
[] 배열이나 리스트 엘리먼트 접근
() 부가적인 표현식을 연산 순서를 바꿔서 연산하게 할 때. 3 * (1+2)
? : 조건 테스트 - 조건 ? true일때리턴값 : false일때리턴값
+ 더하기
- 빼기 혹은 음수
* 곱하기
/ 혹은 div 나누기
% 혹은 mod 나머지
== 혹은 eq 같다
!= 혹은 ne 다르다
< 혹은 lt 보다 작다
> 혹은 gt 보다 크다
<= 혹은 le 작거나 같다
>= 혹은 ge 크거나 같다
&& 혹은 and 논리 AND
|| ('|'가 연속으로 두개) 혹은 or 논리 OR
! 혹은 not 단항 not (true를 false로 false를 true로)
empty 빈 변수 값(null, 빈 문자열, 빈 배열, 엔트리가 없는 맵이나 컬렉션)인가 테스트
func(args) 함수 호출. func는 함수 이름이고 args는 없거나, 한개 혹은 쉼표(,)로 분리된 여러개의 함수 인자이다

EL 표현식은 숫자, 문자열(홑따옴표 혹은 쌍 따옴표와 함께), 불린 값과 null같은 리터럴(상수 값)들도 포함할 수 있다.

EL 표현식이 템플릿 문자열(HTML등)이 오는 곧에도 올 수 있기 때문에, JSP 컨테이너에게 그것이 EL표현식이라고 알려줘야만 한다. 구획문자(delimiter)를 이용해서 하면 된다. EL은 항상 ${ 구획문자로 시작해서 }로 끝난다. 여기 5를 amount라는 변수에 더하는 EL 표현식이 있다.

${amount + 5}

빈 프라퍼티에 5를 더하고자한다면, 프라퍼티 접근 연산자를 사용하면 된다:

${order.amount + 5}

프라퍼티 접근 연산자(.)은 EL에게 빈이나 컬렉션(여기서는 order, 빈이나 java.util.Map 일 수 있다)에서 그 다음에 오는 이름과 같은 프라퍼티(여기서는 amount)를 찾게한다.

대신, 배열 접근 연산자를 사용해도 된다.

${order['amount'] + 5}

[] 안의 값은 프라퍼티의 이름을 나타내는 문자열(이 예제에서 쓰인대로)이거나 아니면 프라퍼티 이름을 값으로 가진 변수 (혹은 다른 EL 표현식)이면 된다.

EL은 동적으로 값을 받도록(dynamic value 혹은 request-time attribute) 지정된 표준(JSTL)이나 커스텀 JSP 액션의 속성에 값을 지정할 때도 사용할 수 있다 :

<c:out value="${order.amount + 5}"/>

JSP 2.0 이전에는 속성에 동적으로 값을 주기 위해 JAVA 표현식을 써야만 했다. 이로인해 우리는 지난 몇년간 수많은 문법 오류에 시달려야만 했다.

마침내 EL 표현식이 페이지 내에서 템플릿 텍스트와 직접적으로 함께 쓰일 수 있게 되었다. 이것은 HTML 엘리먼트와 속성을 동적으로 지정해야 할 때 매우 편리하다.

<input name="firstName" value="${customer.firstName}">

JSP 1.2 에서는, 같은 작업을 하기 위해 JSTL의 <c:out> 액션을 사용해야만 했다. 이것은 서로 다른 형의 두 엘리먼트를 섞어써서 이해하기 매우 어렵다 :

<input name="firstName"
    value="<c:out value="${customer.firstName}"/>" >

새로운 JSTL 1.1 태그 라이브러리 지정자

JSTL 1.1은 JSTL을 JSP 2.0에 통합하기 위한 마이너 스펙 릴리즈이다. 눈에 띄는 변화는 JSTL 1.0의 두개로 분리되있던 라이브러리(한가지는 EL를 사용하고 한가지는 JAVA 표현식을 사용하는)가 EL과 JAVA 표현식 모두를 사용할 수 있는 한가지 라이브러리로 통합된 것이다.

JSTL 1.1의 라이브러리들은 다음과 같은 지정자를 사용해야 한다. 1.0과 다르니 주의해야 한다.

LibraryURIPrefix
Core http://java.sun.com/jsp/jstl/core c
XML processing http://java.sun.com/jsp/jstl/xml x
I18N formatting http://java.sun.com/jsp/jstl/fmt fmt
Database access http://java.sun.com/jsp/jstl/sql sql
Functions http://java.sun.com/jsp/jstl/functions fn

JSTL 1.0을 사용했었다면, 모든 라이브러리의 URI의 중간에 '/jsp' 패스가 들어간 것을 제외하고, 기존의 라이브러리 지정자와 동일함을 알 수 있다. 또한, JSTL 1.1이 EL Function 이라는 새로운 라이브러리를 포함하고 있음도 볼 수 있다. 이것을 짧게 살펴볼 것이다.

새로운 EL 연산자

JSP 페이지에서 매우 자주 필요한 기능 중의 하나는 어떤 조건이 true 일 때 템플릿 텍스트를 포함시키는 기능이다. JSP 1.2와 JSTL 1.1을 이용하면, 이것을 <c:if> 블록을 이용해서 할 수 있다. 하지만, 그건 무척 복잡한 해결책이다. JSP 2.0은 EL에 새로운 조건 연산자를 추가하여 더욱 고상하게 이런 문제를 처리할 수 있다.

조건 연사자는 많은 프로그래밍 언어에 존재하므로(예를 들어 JAVA, C, JavaScript 등). 이미 본적이 있을 것이다. 이 연산자는 불린 조건과 조건이 참일때 사용할 값과 조건이 거짓일 때 사용할 또 다른 값을 취한다. 예제를 보면 어떻게 돌아가는지 확실히 알 수 있을 것이다:

<select name="artist">
 <option value="1" ${param.artist == 1 ? 'selected' : ''}>
 Vesica Pisces
 <option value="2" ${param.artist == 2 ? 'selected' : ''}>
 Cortical Control
 <option value="3" ${param.artist == 3 ? 'selected' : ''}>
 Vida Vierra
</select>

여기서 나는 select 목록에서 입력된 값에 일치하는 값을 가지는 option에 selected 속성을 지정하기 위해 조건 연산자를 사용했다. 만약 조건 (param.artist == 1)이 true이면, 첫번째 결과 ('selected')가 페이지에 추가된다. 그렇지않으면 두번째 값(빈 문자열)이 추가된다.

EL 함수

EL이 JSTL스펙에서 JSP 스펙으로 넘어가면서, 새로운 묘기를 추가했다. 다시말해 함수를 호출하는 방법이다. EL 함수 문법은 단순하다. 인자들을 괄호안에 가지고 있는 함수의 이름:

<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>

${fn:length(myCollection)}

함수는 태그 라이브러리에 속하는 것이고, 함수 이름은 각 페이지의 태그 라이브러리 지시자를 통해 라이브러리에 지정된 prefix를 포함한다.

태그 라이브러리 기술자(TLD; Tag Library Descriptor)는 함수 이름을 함수의 기능을 구현한 자바 클래스의 static 메소드로 매핑한다.

<function>
<description>
컬렉션의 아이템 갯수나 문자열의 문자 갯수를 리턴한다.
</description>
<name>length</name>
<function-class>
org.apache.taglibs.standard.functions.Functions
</function-class>
<function-signature>
int length(java.lang.Object)
</function-signature>
</function>

여기서 가장 주의해서 볼 것은 <function-signature> 엘리먼트이다. 여기에 함수가 리턴할 값의 형에 대한 선언, static으로 구현한 메소드의 이름, 그리고 괄호안에 모든 인수(없거나 1개 이상, 쉼표로 분리)의 형 선언한 것이 포함된다. 리턴 형과 인수의 형은 JAVA 원시형이거나 완전한(패키지까지 포함한) 형 이름이어야 한다.

length() 함수에 대한 static 메소드는 Jakarta Taglibs Standard library에서는 아래와 같이 구현되어있다:

public static int length(Object obj) throws JspTagException {
if (obj == null)
    return 0;
if (obj instanceof String)
    return ((String)obj).length();
if (obj instanceof Collection)
    return ((Collection)obj).size();
if (obj instanceof Map)
    return ((Map)obj).size();

int count = 0;
if (obj instanceof Iterator) {
    Iterator iter = (Iterator) obj;
    count = 0;
    while (iter.hasNext()) {
        count++;
        iter.next();
    }
    return count;
}
if (obj instanceof Enumeration) {
    Enumeration enum = (Enumeration) obj;
    count = 0;
    while (enum.hasMoreElements()) {
        count++;
        enum.nextElement();
    }
    return count;
}
try {
    count = Array.getLength(obj);
    return count;
} catch (IllegalArgumentException ex) {}
    throw new JspTagException("Unsupported type");
}

여기 보듯이, 별로 낯설것이 없다. 이건 그냥 평범한 static 메소드로 인수의 특정한 런타임 형에 따라 length를 구하는 것이다.

여기서 예로든 length() 메소드 외에도, JSTL 1.1 함수 태그 라이브러리는 자주 사용되는 많은 함수들을 포함하고 있다:

함수설명
fn:contains(string, substring) string이 substring을 포함하면 true 리턴.
fn:containsIgnoreCase(string, substring) 대소문자에 관계없이, string이 substring을 포함하면 true 리턴.
fn:endsWith(string, suffix) string이 suffix로 끝나면 true 리턴.
fn:escapeXml(string) string에 XML과 HTML에서 특별한 의미를 가진 문자들이 있으면, XML 엔티티 코드로 바꿔준 뒤 문자열 리턴.
fn:indexOf(string, substring) string에서 substring이 처음으로 나타나는 인덱스 리턴.
fn:join(array, separator) array(배열) 요소들을 separator를 구분자로 하여 연결해서 리턴
fn:length(item) item 이 배열이나 컬렉션이면 요소의 갯수를, 문자열이면 문자의 갯수를 리턴.
fn:replace(string, before, after) string 내에 있는 before 문자열을 after 문자열로 모두 바꿔서 리턴.
fn:split(string, separator) string 내의 문자열을 separator에 따라 나누어서 배열로 구성해 리턴.
fn:startsWith(string, prefix) string이 prefix로 시작하면 true 리턴.
fn:substring(string, begin, end) string에서 begin 인덱스에서 시작해서 end 인덱스에 끝나는 부분(end 인덱스에 있는문자 포함)의 문자열을 리턴.
fn:substringAfter(string, substring) string에서 substring이 나타나는 이후의 부분에 있는 문자열을 리턴.
fn:substringBefore(string, substring) string에서 substring이 나타나기 이전의 부분에 있는 문자열을 리턴.
fn:toLowerCase(string) string을 모두 소문자로 바꿔 리턴.
fn:toUpperCase(string) string을 모두 대문자로 바꿔 리턴.
fn:trim(string) string 앞뒤의 공백(whitespace)을 모두 제거하여 리턴.

마무리

이 글에서, 새로운 EL의 특징과 JSTL 1.1 함수 라이브러리 등 JSP 스펙에 도입된 EL을 다루었다. 다음 회에서는 JSP의 오류 페이지(error-page)의 향상과 jsp:id 속성의 이점, 새로운 배치 기술자(deployment descriptor)의 형식, JSP 2.0 이 JSP와 XML 사용을 어떻게 쉽게 만들어주는가, 그리고 커스텀 태그 라이브러리에 관한 사항들을 다룰 것이다.

JSP 2.0의 새로운 기능들을 접해보고 싶다면, Apache Tomcat 5를 사용해보라고 권하고 싶다. 톰캣은 새로운 JSP 스펙을 최초로 구현한 JSP 컨테이너이다. Jakarta Project 사이트에서 구할 수 있다.