메인 컨텐츠로 가기

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관 보기.

developerWorks에 처음 로그인하면 developerWorks프로파일이 생성됩니다.귀하의 프로파일에서 동의하신 내용이 공개되지만 이 사항은 언제든지 변경 가능합니다. 귀하의 성명(숨김으로 체크되어 있어도 표시됩니다)과 디스플레이 이름은 게시한 컨텐츠나 사이트 엑세스시 표시됩니다.

모든 정보가 안전하게 전송되었습니다.

  • 닫기 [x]

처음 developerWorks에 로그인할 때 프로파일이 작성되므로, 이를 위해 디스플레이 이름을 선택해야 합니다. 선택하신 디스플레이 이름은 developerWorks에 게시한 컨텐츠에 표시됩니다.

3글자 이상 31글자 이하의 길이로 사용 가능합니다. dW커뮤니티 내에서는 보안상 이메일주소를 제외한 다른 이름을 지정하셔야 합니다.

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관 보기.

모든 정보가 안전하게 전송되었습니다.

  • 닫기 [x]

불신자를 위한 JSF: JSF 타입 전환과 검증

JSF 타입 전환과 검증 프레임워크를 사용해 데이터 모델 무결성 확보하기

Richard Hightower, CTO, ArcMind
Rick Hightower는 ArcMind Inc.의 CTO로 일한다. J2EE 개발에 XP를 적용하는 것을 다룬 Java Tools for Extreme ProgrammingProfessional Struts의 공동 저자이기도 하다.
Paul Tabor, CTO, Rizon Software
Paul Tabor는 Rizon Software의 핵심 기술 부서를 담당하고 있다. Paul Tabor는 지난 몇 년간 Rick Hightower와 몇 번의 프로젝트를 함께 했다. Paul과 Rick은 JSF를 스프링, 하이버네이트와 함께 몇몇 기술 난도가 높은 애플리케이션에서 편하게 사용할 수 있도록 하고 있다. Paul은 ptabor@rizonsoftware.com 또는 ptabor@arc-mind.com을 통해 연락할 수 있다.

요약:  JSF(JavaServer Faces)는 표준 타입 전환, 검증, 그리고 메시징 프레임워크를 제공하여 대부분의 폼 처리시 필요한 데이터 모델 검증 작업을 편리하게 해줍니다. 본 불신론자를 위한 JSF 연재의 세 번째 기사에서, Paul Tabor와 RickHightower는 아무리 복잡한 애플리케이션이라 하더라도, 얼마나 간단하게 타입 전환과 검증 기능을 끼워 넣을 수 있는지 보여줄 것입니다.

편집자 주: 이 글을 발행한 후 썬에서 JSF 1.2를 오픈 소스로 만들었습니다(라이선스는 CDDL). 새 프로젝트 페이지로 가는 링크는 참고자료를 보세요.
현재 JEE6에 통합된 JSF 1.2를 시작하는 방법에 관한 자세한 내용은 Richard Hightower의 튜토리얼 연재를 보세요.

이 연재 자세히 보기

원문 게재일:  2008 년 9 월 23 일
난이도:  중급 영어로:  보기
페이지뷰:  1370 회
의견:  


이번 달, Rizon Software의 CTO Paul Tabor는 JSF에 관한 미신을 없애자는 내 제안에 응해 나와 함께 이 글을 쓴다. 본 기사에서 JSF 타입 전환과 검증 프레임워크의 개념에 대해 살펴볼 것이며, 이것들은 여러분이 생각하는 것보다 훨씬 유연하며 사용하기 간단할 것이다.

먼저 JSF 생명 주기에 적용되어 있는 타입 전환과 검증 절차를 소개하고, 간단한 JSF 애플리케이션에 있는 기본 타입 전환과 검증을 설명하겠다. 또한 상황에 적절하게 여러분이 직접 구현체를 만들고 그것을 끼워 넣는 방법도 살펴보겠다. Rick이 앞선 기사에서도 그래왔듯이, 이론과 실습을 겸비하여 개념을 살펴본 다음 동작하는 예제도 같이 볼 것이다. 예제 애플리케이션은 다소 기본적이긴 하지만, 자주 사용하는 타입 전환과 검증 사용 방법을 살펴볼 것이다.

JEE WARTAC 블로그

저자 Rick Hightoweer는 JEE WARTAC 블로그에서 JSF에 대해 더 구체적으로 다루고 있다. 이 블로그는 그가 최근 개발에서 사용하는 도구들을 주제로 다루는데 JSF, Facelets, Hibernate, Spring, Tomahawk, MyFaces, RDBMS 시스템, iBatis 등이다.

본 예제의 기본 빌드 환경은 메이븐(Maven)이며, 앤트(Ant) 스크립트도 제공한다. 예제 소스는 본 기사 맨 위와 아래에 있는 Code 아이콘을 클릭하여 받을 수 있다. 예제 설치 방법은 이전 기사와 같다. 예제 애플리케이션을 앤트나 메이븐으로 실행하기 위한 빌드 환경 설정에 대한 자세한 내용은 참고자료를 보기 바란다.

타입 전환과 검증

JSF 웹 애플리케이션에서 타입 전환과 검증을 사용할 때, 굳이 JSF 생명 주기를 이해해야 하는 것은 아니다. 하지만 타입 전환과 검증을 자세히 살펴보기 전에 몇 가지 기본 사항을 점검하는 게 좋겠다. 게다가, JSF 생명 주기에 대한 약간의 노하우가 웹 애플리케이션 개발 노력을 줄이는 데 도움이 될 것이다. 그리고 JSF의 끼워 넣기(pluggable) 기능을 이해하는 것에도 도움이 된다.

그림 1은 "기본 JSF 생명 주기"라 부르는 것으로, 이 때 기본이라는 것은 폼 값이 전달되었을 때 일반적인 JSF 요청-응답 시나리오를 말한다.


그림 1. 기본 JSF 생명 주기
JSF 기본 생명 주기 다이어그램

분명, 시나리오에 따라 이 그림에서 색칠되어 있는 생명 주기에 다르게 영향을 줄 것이다. 그것에 대해서는 이 글 후반부에 다루겠다. 지금은, 타입 전환과 검증 절차가 요청 값 적용하기(apply request values), 검증 거치기(process validations), 응답 보여주기(render response) 단계에서 발생한다는 것을 알아두기 바란다.

곧이어 왜 타입 전환과 검증이 이 단계들에서 일어나는지 설명할 것이다. 하지만 그 전에 기본적인 질문부터 해결하자. 타입 전환(conversion)이란 무엇인가? 간단하게 얘기하면, 올바른 객체나 타입의 데이터를 확인하는 과정이라고 할 수 있다. 다음 두 개의 타입 전환을 살펴보자.

  • java.util.Date로 타입을 전환할 수 있는 문자열(string)
  • Float으로 타입을 전환할 수 있는 문자열

검증은 예상한 데이터를 담고 있는지 확인하는 것이다.

  • MM/yyyy 형식의 java.util.Date
  • 1.0과 100.0 사이의 값을 가진 Float

주요 생명 주기 단계

타입 전환과 검증의 주요 목적은 모델 데이터를 업데이트하기 전에 값이 무해한지 확인하는 것이다. 그렇게 함으로써, 실제 데이터를 가지고 뭔가를 하려고 애플리케이션 메서드를 호출하는 시점이 왔을 때, 모델의 상태가 안전하다는 가정하에 작업을 할 수 있다. 개발자들은 타입 전환과 검증 덕에 입력받은 데이터에 대해 null 확인, 길이, 범위 경계 등 지루한 확인 작업을 떠나 비즈니스 로직에 집중할 수 있게 된다.

자, 그러면 타입 전환과 검증 절차를 실행하는 시점이 빈(bean) 모델에 컴포넌트 데이터를 바인딩하기 전에 수행함을 이해할 수 있을 것이다. 그림 1에서 볼 수 있듯이, 타입 전환은 요청 값 적용하기 단계에서 수행하고 검증은 검증 처리하기 단계에서 수행한다. 이 단계들은 그림 2에 더 눈에 띄게 표시해뒀다.


그림 2. 타입 전환과 검증 단계
전환과 검증 단계의 JSF 생명 주기 다이어그램

중개 속성에 대하여

그림 2에서 살펴본 타입 전환과 검증 수행은 UIInput 컴포넌트의 immediate 속성이 false로 설정되어 있을 때의 애플리케이션 흐름을 보여준 것이다. 해당 속성이 true였다면, 타입 전환과 검증을 생명 주기 초기에 요청 값을 적용하는 단계에서 실행했을 것이다(그림 3 참조). immediate 속성을 사용하는 것에 대한 자세한 내용은 이 글의 범위를 벗어나지만, 이것이 유용한 경우가 있다. (본 연재의 이전 기사에서 다룬 내용을 떠올린다면) 동적인 리스트를 맵핑하거나 (UICommand 컴포넌트와 함께 사용할 때) 넘기자마자 검증(bypass validation)을 하고 싶을 때에 해당한다. 애플리케이션에서 값을 넘기자마자 검증하는 것이 유용한 경우가 또 언제 있을까?

그림 3은 immediate 속성이 true일 때, JSF 애플리케이션 생명 주기 중에서 언제 타입 전환과 검증 처리를 하는지 보여준다.


그림 3. immediate 속성이 true일 때
immediate 속성이 true일 때 JSF 생명 주기 다이어그램

동작하는 예제

지금부터 예제 애플리케이션을 사용해 앞에서 논의했던 내용을 실습할 것이다. 이번 예제 애플리케이션은 JSF의 타입 전환과 검증 기능을 살펴볼 것이다. 정말로 애플리케이션은 매우 간단하며 필요한 것이 그리 많지 않다. 실제로 사용할 애플리케이션을 만드는 것이 목적이 아니기 때문이다! 본 예제 애플리케이션은 다음의 것들을 보여줄 것이다.

  • 표준 JSF 타입 전환을 사용하여 폼 필드 데이터 타입 전환하기
  • 표준 JSF 검증 컴포넌트를 사용하여 필드 데이터 검증하기
  • 커스텀 컨버터(convertor)와 검증기(validator) 만들기
  • 커스텀 컨버터와 검증기를 faces-cofing.xml에 등록하기
  • 기본 에러 메시지 커스터마이징하기

예제 애플리케이션은 간단한 사용자 등록 폼이다. 목표는 사용자의 이름, 나이, 이메일 주소, 전화번호 같은 정보를 가져오는 것이다. 그런 다음, 가져온 데이터가 모델에 적합한지 JSF 타입 전환과 검증을 사용하는 방법을 보여줄 것이다.

애플리케이션은 JSP 페이지 세 개로 구성되어 있다.

  • UserRegistration.jsp 페이지로 리다이렉트하는 index.jsp
  • 애플리케이션 폼 피드를 가지고 있는 UserRegistration.jsp
  • 등록한 사용자 정보를 보여주는 result.jsp

JSF 타입 전환 처리를 코딩할 때 사용할 수 있는 옵션들을 살펴보면서 시작하겠다.


JSF 타입 전환

앞서 언급했듯이 타입 전환은 데이터가 올바른 객체나 타입인지 확인하는 과정이다. 그렇게 함으로써 문자열 값을 Date 객체나 기본 타입인 float 또는 Float 객체로 전환할 수 있다. 내장된 컨버터를 사용하거나 자신의 컨버터를 직접 만들 수 있다.

JSF는 다양한 표준 데이터 컨버터를 제공한다. 물론 Converter 인터페이스를 구현하여 직접 만든 컨버터를 사용할 수도 있다. 다음 표는 컨버터의 id와 그에 대응하는 구현체 클래스로 JSF에서 간단한 데이터 타입 전환시 사용할 수 있다. 대부분의 데이터 타입 전환은 자동으로 이루어진다.

javax.faces.BigDecimal javax.faces.convert.BigDecimalConverter
javax.faces.BigInteger javax.faces.convert.BigIntegerConverter
javax.faces.Boolean javax.faces.convert.BooleanConverter
javax.faces.Byte javax.faces.convert.ByteConverter
javax.faces.Character javax.faces.convert.CharacterConverter
javax.faces.DateTime javax.faces.convert.DateTimeConverter
javax.faces.Double javax.faces.convert.DoubleConverter
javax.faces.Float javax.faces.convert.FloatConverter

그림 4는 사용자의 나이에 대한 기본 컨버터를 사용하고 있다. JSF 태그는 다음과 같이 사용하면 된다.

    <!-- UserRegistration.jsp -->
    <h:inputText id="age" value="#{UserRegistration.user.age}"/>


그림 4. 사용자 등록: 나이에 적용한 기본 컨버터
디폴트 전환

모든 경우를 다루는 컨버터

UserRegistration.user.ageint 타입으로 값을 바인딩한다. 모든 기본 타입 또는 BigInteger/BigDecimal 등을 바인딩할 때, JSF는 표준 컨버터 중에서 적당한 것을 선택하여 사용한다. 하지만 직접 <f:converter/> 태그를 다음과 같이 사용하여 타입을 더 상세히 기술할 수도 있다.

<!-- UserRegistration.jsp -->
<h:inputText id="age" value="#{UserRegistration.user.age}">
             <f:converter id="javax.faces.Short"/>
</h:inputText>

그림 5는 JSF가 표준 컨버터를 사용하는 경우를 보여준다. 이 경우, 입력한 age가 int 타입이라도, 값이 short 타입이 아니기 때문에 타입 전환에 실패한다.


그림 5. f:converter 태그 사용하기
f:converter 태그 사용하기

날짜 형식 패턴 선택하기

JSF가 기본 타입과 몇몇을 잘 다루지만, 날짜 타입을 다룰 때는 반드시 <f:convertDateTime/>이라는 타입 전환 태그를 사용해야 한다. 이 태그는 java.text 패지키를 기반으로 하고 있으며, short, long, 그리고 커스텀 패턴을 사용할 수 있다. 예제는 다음과 같다.

<!-- UserRegistration.jsp -->
<h:inputText id="birthDate" value="#{UserRegistration.user.birthDate}">
             <f:convertDateTime pattern="MM/yyyy"/>
</h:inputText>

이 예제는 <f:convertDateTime/>을 사용하여 사용자의 생일 값이 MM/yyyy(월/년) 형태의 Date 타입으로 전환할 수 있는지 확인한다. JSF의 java.text.SimpleDataFormat(참고자료)을 참조하여 모든 가용한 패턴을 확인하기 바란다.

추가적인 패턴

날짜와 시간 타입 전환 외에도, JSF는 퍼센트나 통화에 대한 타입 전환도 제공한다. 이 컨버터는 (콤마를 사용한) 그루핑, 소숫점 자릿수, 통화 기호 등을 제공한다. 예를 들어, 다음 예제에서는 <f:convertNumber/>를 사용해 통화를 다루고 있다.

<!-- UserRegistration.jsp -->
<h:inputText id="salary" value="#{UserRegistration.user.salary}">
             <f:convertNumber maxFractionDigits="2"
                       groupingUsed="true"
                       currencySymbol="$"
                       maxIntegerDigits="7"
                       type="currency"/>
</h:inputText>

그림 6은 적절하지 않은 통화 값에 대한 타입 전환 에러를 보여주고 있다.


그림 6. f:convertNumber 태그 사용하기
f:convertNumber 태그 사용하기

커스텀 컨버터

커스텀 데이터 컨버터는 애플리케이션에 특화된 값 객체(value object)에 데이터를 넣어야 할 경우 유용하다. 그 예는 다음과 같다.

  • 문자열을 PhoneNumber 객체로(PhoneNumber.areaCode, PhoneNumber.prefix, ...)
  • 문자열을 Name 객체로(Name.first, Name.last)
  • 문자열을 ProductCode 객체로(ProductCode.partNum, ProductCode.rev, ...)

커스텀 컨버터를 만들려면 반드시 다음의 것들을 해야 한다.

  1. Converter 인터페이스를 구현한다(javax.faces.convert.Converter).

  2. getAsObject 메서드를 구현한다. 이 메서드에서 필드(문자열)를 객체(예. PhoneNumber)로 전환한다.

  3. getAsString 메서드를 구현한다. 이 메서드에서 객체(예. PhoneNumber)를 문자열로 전환한다.

  4. 커스텀 컨버터를 Faces 컨텍스트에 등록한다.

  5. 컨버터를 JSP에 <f:converter/> 태그를 사용하여 삽입한다.

이 각 단계가 JSP 애플리케이션 생명 주기에 어떻게 들어 맞는지 확인할 수 있다. 그림 7을 보면, JSF는 요청 값 적용하기 단계에서 커스텀 컨버터의 getAsObject 메서드를 호출한다. 이 단계가 바로 컨버터가 반드시 요청 문자열을 원하는 객체 타입으로 변환하는 곳이다. 그러고 나서 해당 객체를 대응하는 JSF 컴포넌트에 있는 저장소로 반환한다. 값이 뷰에 보일 때는, JSF가 응답 보여주기 단계에서 getAsString 메서드를 호출할 것이다. 이것은 컨버터가 객체 데이터를 다시 문자열로 표현하는 것까지 책임진다는 것을 뜻한다.


그림 7. 커스텀 컨버터 getAsObject와 getAsString 메서드
getAsObject, getAsString 커스텀 컨버터 메서드

커스텀 컨버터 만들기

Converter 인터페이스, getAsObjectgetAsString을 구현하고 컨버터를 Faces 컨텍스트에 등록하는 과정을 예제를 통해 살펴보겠다.

이번 예제의 목표는 문자열 하나를 PhoneNumber 객체로 전환하는 것이다. 차근 차근 살펴보자.

1단계: Converter 인터페이스 구현하기

이번 단계는 Converter 인터페이스를 구현한다.

import javax.faces.convert.Converter;
import org.apache.commons.lang.StringUtils;
...

public class PhoneConverter implements Converter {
	...

}

2단계: getAsObejct 메서드 구현

필드 값을 PhoneNumber 객체로 변환한다.

public class PhoneConverter implements Converter {

	...

	public Object getAsObject(FacesContext context, 
					UIComponent component, String value) {
	    if (StringUtils.isEmpty(value)){ return null;}

	    PhoneNumber phone = new PhoneNumber();

	    String [] phoneComps = StringUtils.split(value," ,()-");

	    String countryCode = phoneComps[0];

	    phone.setCountryCode(countryCode);

	    if ("1".equals(countryCode)){
	        String areaCode = phoneComps[1];
	        String prefix = phoneComps[2];
	        String number = phoneComps[3];
	        phone.setAreaCode(areaCode);
	        phone.setPrefix(prefix);
	        phone.setNumber(number);
	    }else {
	        phone.setNumber(value);
	    }

	    return phone;
	}	
}

3단계: getAsString 메서드 구현하기

PhoneNumber 객체를 문자열로 변환한다.

public class PhoneConverter implements Converter {

	...

	public String getAsString(FacesContext context, 
				UIComponent component, Object value) {
		return value.toString();
	}


}

public class PhoneNumber implements Serializable {

	...

	public String toString(){
		if (countryCode.equals("1")){
			return countryCode + " " + areaCode 
							   + " " + prefix + " " + number;
		}else{
			return number;
		}
	}


}

4단계: 커스텀 컨버터를 faces 컨텍스트에 등록

4단계는 두 가지 방법으로 할 수 있다. 첫 번째 방법은 PhoneConverter 클래스를 (예를 들어) archmind라는 id로 등록하는 것이다. 그렇게 등록한 id를 나중에 JSP 페이지의 <f:converter/> 태그에서 사용할 수 있다. 4단계 첫 번째 방법에 대한 코드는 다음과 같다.

<converter>

  <converter-id>arcmind.PhoneConverter</converter-id>

  <converter-class>com.arcmind.converters.PhoneConverter</converter-class>
      
</converter>

다음으로, PhoneConverter 클래스가 모든 PhoneNumber 객체를 자동으로 다루도록 설정할 수 있는데, 방법은 다음과 같다.

<converter>
  
  <converter-for-class>com.arcmind.value.PhoneNumber</converter-for-class>

  <converter-class>com.arcmind.converters.PhoneConverter</converter-class>
      
</converter>

5단계: JSP에서 converter 태그 사용하기

이 단계를 실행하는 방법은 당연히 위에서 어떤 방법으로 등록했느냐에 따라 달라진다. PhoneConverter 클래스를 arcmind라는 id로 등록했다면, <f:converter/> 태그를 다음과 같이 사용할 수 있다.

<h:inputText id="phone" value="#{UserRegistration.user.phone}">
        <f:converter  converterId="arcmind.PhoneConverter" />
</h:inputText>  

PhoneConverter 클래스가 모든 PhoneNumber 객체를 다루도록 설정했다면, <f:converter/> 태그를 JSP에 사용할 필요는 없다.

<h:inputText id="phone" value="#{UserRegistration.user.phone}">
        [Look mom no converter!]
</h:inputText>  

자, 지금까지 살펴본 타입 전환 코드를 사용한 예제 애플리케이션에 사용해 봤다. 애플리케이션은 다음과 같이 생겼다.


그림 8. 타입 처리를 적용한 예제 애플리케이션
타입 처리를 적용한 예제 애플리케이션

JSF 검증

다시 돌아가서, JSF 검증은 애플리케이션 데이터가 예상하는 내용을 가지고 있는지 확인하는 것이라고 했다. 즉 예를 들면 다음과 같다.

  • java.util.Date가 MM/yyyy 형태인지
  • Float이 1.0과 100.0 사이의 값인지

JSF에서 사용할 수 있는 네 가지 검증이 있다.

  • 내장 검증 컴포넌트
  • 애플리케이션 수준 검증
  • 커스텀 검증 컴포넌트(Validator 인터페이스를 구현한 것)
  • 빈(bean)에 있는 검증 메서드(inline)

이어서 각각을 하나씩 실습하며 살펴보겠다.

JSF 검증 생명 주기와 컴포넌트

그림 9는 사용자 등록 폼의 이름 필드에 대한 생명 주기를 살펴본 것이다. 코드는 유사코드(pseudo-code)로 표현했다.


그림 9. JSF 생명 주기에서 검증
JSF 생명 주기에서 검증의 실례

JSF가 제공하는 표준 검증 컴포넌트 목록은 다음과 같다.

  • DoubleRangeValidator: 컴포넌트의 로컬 값은 반드시 숫자 타입이어야 한다. 그리고 최소값과 최대값 범위 내에 포함되어야 한다.

  • LongRangeValidator: 컴포넌트의 로컬 값은 반드시 숫자 타입이어야 하며 long으로 타입 전환이 가능해야 한다. 그리고 최소값과 최대값 범위 내에 포함되어야 한다.

  • LengthValidator: 반드시 문자열 타입이어야 한다. 길이는 최소값과 최대값 범위 내에 포함되어야 한다.

표준 검증

예제 애플리케이션에서 사용자의 나이는 양의 정수(byte, short, int) 값이어야 한다. -2라는 나이는 말이 안 되기 때문이다. 따라서 필드에 추가적인 검증을 적용하고 싶을 것이다. 간단한 검증 코드를 추가하여 나이 필드에 대한 모델 무결성을 보장하자.

<h:inputText id="age" value="#{UserRegistration.user.age}">
          <f:validateLongRange maximum="150"
                                  minimum="0"/>
</h:inputText>

일단 나이 필드에 적용하고 나면, 이름 필드에도 길이 제약을 적용하고 싶어질 것이다. 해당 검증 코드는 다음과 같다.

<h:inputText id="firstName"
                value="#{UserRegistration.user.firstName}">
	<f:validateLength minimum="2" 
						 maximum="25" />
</h:inputText>

그림 10은 위에서 사용한 표준 검증 예제에서 생성한 기본 검증 메시지를 보여준다.


그림 10. 표준 검증 에러 메시지
표준 검증 에러 메시지와 애플리케이션 예의 스크린샷

대부분의 경우에 이렇게 사용할 수 있지만, JSF 내장 검증에는 몇 가지 제약이 따른다. 이메 검증이나 전화 번호, URL, 날짜 등을 다룰 때는 자신만의 검증기가 필요할 것이다. 이것들은 잠시 뒤에 살펴보자.


애플리케이션 수준 검증

개념적으로, 애플리케이션 수준 검증은 실제 비즈니스 로직 검증에 해당한다. JSF는 폼 또는 필드 검증을 비즈니스 로직 검증과 분리했다. 기본적으로, 애플리케이션 수준 검증은 추가적인 뒷단의 빈(bean) 메서드 코드를 작성하여 이미 바인딩된 값을 가지고 있는 데이터를 검증하는 것이다. 예를 들어 쇼핑 카트의 경우, 폼 수준 검증은 일력된 가격이 유효한지 확인하지만 비즈니스 검증에서 이 값이 계좌 한도를 넘지는 않는지 확인해야 할 것이다. 이것을 JSF에서 SOC(seperation of concern: 관심사 분리)의 한 예라고 볼 수 있다.

예를 들어, 사용자가 액션 메서드에 묶여있는 버튼을 클릭했다고 해보자. 그럼 애플리케이션 실행 단계로 넘어간다(그림 1 참조). 모델 데이터를 조작하기에 앞서, 애플리케이션 비즈니스 룰에 따라 입력받은 데이터가 적절한지 확인하는 코드를 추가할 것이다.

예제 애플리케이션에서, 사용자가 Register 버튼을 클릭할 수 있고, 이 버튼에 애플리케이션 컨트롤러의 register() 메서드가 묶여있다. 우리는 검증 코드를 register() 메서드에 추가하여 이름 필드의 값이 비어 있거나 null이 아닌지 확인할 수 있다. 이 경우 필드가 null이면, 메시지를 FacesContext에 추가하여 연관된 컴포넌트를 현재 페이지로 되돌려야 한다.

이번 것은, 비즈니스 룰 로직에 대한 좋은 예제는 아니었다. 사용자가 자신의 계좌 한도를 넘는지 안 넘는지 확인하는 것이 더 좋은 예제다. 여기서는, 필드가 비어있는 값인지 확인하는 대신 현재 사용자가 이미 시스템에 존재하는 사용자인지 검사하겠다.

그림 11에서 그 과정을 살펴볼 수 있다.


그림 11. 검증 애플리케이션-레벨
검증 애플리케이션-레벨의 다이어그램

register() 메서드에서, 메시지를 어떻게 FacesContext${formId}:${fieldId}로 추가하는지 살펴보자. 그림 12는 메시지와 컴폰너트 id 사이의 관계를 보여주고 있다.


그림 12. 검증 메시지
검증 메시지

애플리케이션 수준 검증에 대한 찬성과 반대 의견

애플리케이션 수준 검증은 분명히 구현하기 간단한 방법이다. 하지만 이런 형태의 검증은 다른 형태의 검증(표준, 커스텀, 컴포넌트)을 마친 뒤에 발생한다.

애플리케이션 수준 검증의 장점은 다음과 같다.

  • 구현하기 쉽다.
  • 별도의 클래스로 나눌 필요 없다(커스텀 검증기).
  • 검증기를 등록하기 위한 페이지 조작도 필요없다.

애플리케이션 수준 검증의 단점은 다음과 같다.

  • 다른 폼 검증(표준, 커스텀)을 거친 후에 일어난다.
  • 검증 로직이 빈 메서드로 제약적이다. 즉 재사용에 제한이 있다.
  • 규모가 크거나 팀 환경에서 관리하기 힘들다.

무엇보다, 애플리케이션 수준 검증은 비즈니스 로직 검증이 필요할 때에만 사용해야 한다.


커스텀 검증 컴포넌트

표준 JSF 검증기가 지원하지 않는 이메일 주소와 zip 코드 등의 데이터 타입을 지원하는 커스텀 검증기 컴포넌트를 만들 필요가 있을 것이다. 또한 최종 사용자에게 보이는 메시지를 변경하고 싶을 때에도 자신만의 검증기를 만들고 싶을 수 있다. JSF를 사용할 때, 웹 애플리케이션에서 재사용 가능한 검증 컴포넌트를 쉽게 추가할 수 있다.

MyFaces는 오픈 소스 JSF 구현체로, 표준 JSF에 포함되어 있지 않은 다양한 부가적인 검증기를 제공한다. 참고자료에서 MyFaces에 대해 참조하기 바란다.

커스텀 검증기를 만드는 방법은 다음과 같다. 하나씩 차례대로 살펴보자.

  1. Validator 인터페이스(javax.faces.validator.Validator)를 구현하는 클래스를 만든다.

  2. validate 메서드를 구현한다.

  3. 커스텀 검증기를 faces-config.xml 파일에 등록한다.

  4. JSP에서 <f:validator/> 태그를 사용한다.

자, 그럼 차례대로 커스텀 검증기를 만드는 예제를 살펴보자.

1단계: Validator 인터페이스 구현하기

첫 번째 단계는 Validator 인터페이스를 구현하는 것이다.

import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;
...
public class ZipCodeValidator implements Validator{
	private boolean plus4Required;
	private boolean plus4Optional;

	/** Accepts zip codes like 85710 */
	private static final String ZIP_REGEX = "[0-9]{5}";
	
	/** Accepts zip code plus 4 extensions like "-1119" or " 1119" */
	private static final String PLUS4_REQUIRED_REGEX = "[ |-]{1}[0-9]{4}";
	
	/** Optionally accepts a plus 4 */
	private static final String PLUS4_OPTIONAL_REGEX = "([ |-]{1}[0-9]{4})?";
	...
}

2단계: validate 메서드 구현하기

다음으로 validate 메서드를 구현한다.

public void validate(FacesContext context, 
		UIComponent component, Object value) throws ValidatorException {
		 
      /* Create the correct mask */
      Pattern mask =  null; 
		 
      /* more on this method later */
      initProps(component);

      if (plus4Required){
            mask = Pattern.compile(ZIP_REGEX + PLUS4_REQUIRED_REGEX);
      } else if (plus4Optional){
            mask = Pattern.compile(ZIP_REGEX + PLUS4_OPTIONAL_REGEX);
      } else if (plus4Required && plus4Optional){
            throw new IllegalStateException("Plus 4 is either optional or required");
      }
      else {
            mask = Pattern.compile(ZIP_REGEX);
      }

            /* Get the string value of the current field */
      String zipField = (String)value; 
		 	
            /* Check to see if the value is a zip code */
    Matcher matcher = mask.matcher(zipField);
	     
    if (!matcher.matches()){
	     	
       FacesMessage message = new FacesMessage();
       message.setDetail("Zip code not valid");
       message.setSummary("Zip code not valid");
       message.setSeverity(FacesMessage.SEVERITY_ERROR);
       throw new ValidatorException(message);
    }
 }

3단계: 커스텀 검증기를 FacesContext에 등록한다

커스텀 검증기를 FacesContext에 등록하는 방법은 이제 익숙할 것이다.

  <validator>

    <validator-id>arcmind.zipCodeValidator</validator-id>

    <validator-class>com.arcmind.jsfquickstart.validation.ZipCodeValidator
	</validator-class>

  </validator>

4단계: JSP에서 <f:validator/> 태그 사용하기

<f:validator/> 태그는 zipCodeValidator를 사용하겠다고 선언하고 있다. <f:attribute/> 태그는 plus4Optional 속성을 true로 설정했다. 이 속성 정의가 검증기가 아니라 inputText라는 것에 유의하라!

  <h:inputText id="zipCode" value="#{UserRegistration.user.zipCode}">
      <f:validator validatorId="armind.zipCodeValidator"/>
      <f:attribute name="plus4Optional" value="true"/>
  </h:inputText>

zipCode inputText 컴포넌트의 plus4Optional 속성을 읽기 위해 다음과 같이 한다.

private void initProps(UIComponent component) {
  Boolean optional = Boolean.valueOf((String) component.getAttributes().
                                   get("plus4Optional"));
  Boolean required = Boolean.valueOf((String) component.getAttributes().
                                   get("plus4Required"));
  plus4Optional = optional==null ? plus4Optional : 
					     optional.booleanValue();
  plus4Required = required==null ? plus4Optional : 
					     required.booleanValue();
}

전반적으로, 커스텀 검증기를 만드는 것은 매우 간단하고 여러 애플리케이션에서 재사용할 수 있다. 단점은 반드시 별도 클래스를 작성해야 하고 faces 컨텍스트에 검증기를 등록해야 한다는 것이다. 하지만 이 검증기를 간편하게 사용하는 커스텀 태그를 만들어 마치 커스텀 검증기를 내장 검증기처럼 사용하도록 한 단계 더 발전시킬 수 있다. 이메일 검증과 같이 흔히 사용하는 것은, 이 방법을 적용해 코드 재사용과 애플리케이션 일관성을 중요하게 생각하는 설계 철학을 지원할 수 있다.


빈 메서드 내부의 검증 메서드

검증 클래스를 별도로 만드는 것에 대한 대안으로, 커스텀 검증 로직을 검증 대상이 되는 빈 메서드 내부에 구현할 수도 있는데 이 때 해당 메서드의 시그너처는 Validator 인터페이스의 validate 메서드와 같아야 한다. 예를 들어, 다음과 같은 메서드를 작성할 수 있다.

[SomeBackingBean.java]

public void validateEmail(FacesContext context, 
                          UIComponent toValidate,
                          Object value) {
    String email = (String) value;

    if (email.indexOf('@') == -1) {
       ((UIInput)toValidate).setValid(false);

        FacesMessage message = new FacesMessage("Invalid Email");
        context.addMessage(toValidate.getClientId(context), message);
    }

}

이 메서드는 다음 코드처럼 validator 속성을 사용하여 JSF 태그에서 사용할 수 있다.

  <h:inputText id="email" 
               value="#{UserRegistration.user.email}"
               validator="#{UserRegistration.validateEmail}"   
               required="true">
  </h:inputText>

JSF가 사용하는 validateEmail 메서드는 user.email 모델 속성에 묶여있는 inputText 컴포넌트 값에 커스텀 검증을 수행한다. 만약 이메일 형식이 올바르지 않다면, 메시지를 연관된 컴포넌트의 faces 컨텍스트에 추가한다. 이제, 이 검증이 빈에 존재한다는 것에 대해 생각해보자. 왜 로컬 빈 속성을 직접 검사하지 않고 컴포넌트와 연관을 맺고 있는 값을 검사해야 할까? 힌트를 주자면, 앞선 생명 주기 그림을 살펴보기 바란다. 물론 당장 생각이 나지 않는다고 해서 걱정할 것은 없다. 이 기사 마지막에 그 이유에 대해 설명하겠다.

기본 검증

위의 email 태그에 있는 required 속성을 주의깊게 살펴보자. required 속성은 기본 검증과 관련이 있다. 이 속성이 true면 관련있는 컴포넌트는 반드시 값을 가지고 있어야 한다. 여기서 하나 주목할 것은 required 값이 false면, 아무런 검증도 해당 태그나 컴포넌트에 적용하지 않는다. 그럼 JSF는 해당 컴포넌트의 검증을 하지 않고 해당 값과 컴포넌트의 상태를 변경하지 않는다.

그림 13은 지금까지 논의한 검증 폼을 살펴본 것이다.


그림 13. 검증 개요
검증 폼의 다이어그램 개요

커스텀 메시지

아마 알아차렸을 수도 있겠지만, JSF가 제공하는 기본 타입 전환과 검증 메시지는 다소 장황하고 잘못된 값을 입력한 최종 사용자에게 혼란을 줄 수 있고 화가 나게 할 수 있다. 다행히, JSF가 제공하는 기본 메시지를 자신만의 메시지 리소스 번들을 만들어 변경할 수 있다. jsf-impl.jar(또는 이와 비슷한) 파일에 message.properties 파일이 들어있고 이 파일에는 그림 14에 보이는 기본 메시지가 들어있다.


그림 14. 기본 JSF 타입 전환과 검증 메시지
JSF 기본 전환과 검증 메시지

message.properties 파일을 작성하고 메시지 리소스 번들을 faces 컨텍스트에 특정 로케일에 따라 설정해주면 기본 메시지를 변경할 수 있다. 그림 15를 참조하라.


그림 15. 메시지 리소스 번들 변경하기
메시지 번들

참고자료에서 JSF에서 커스텀 타입 전환과 검증 메시지를 작성하는 것과 관련된 더 많은 정보를 참조하라.


JSF 생명 주기 다루기

이 글에서 독자들이 고민할 거리는 조금 남겨두었다. 그것들을 이제 살펴보자! 앞서 commandLink 또는 commandButtons 같은 UICommand 버튼에 직접 속성(immediate attributes)을 사용하는 방법에 대해 언급하고 어떤 종류의 애플리케이션 시나리오에서 검증을 제외하고 싶을지 생각해보라고 했다.

기본적으로, 사용자가 데이터를 입력하는 모든 시나리오에서 데이터 검증은 필요하다. 하지만 전반적으로 데이터 요소가 부가적인 것이라면, 검증을 할 필요가 없다. JSF 생명 주기에서 검증 단계를 조작하는 방법 중 하나는 UICommand 컴포넌트의 immediate 속성을 사용하는 것이다. 이 속성은 검증 단계(혹은 이 다음 단계인 애플리케이션 호출 단계)를 처리하기 전에 요청 값 적용하기 단계에서 액션을 호출하도록 강제할 수 있다.

immediate 속성은 검증을 거치지 않고 표준 내비게이션 룰에 따라 페이지 흐름을 제어할 수 있다. 이런 기술을 (사용자가 Skip 버튼을 눌러 다음 뷰로 넘거갈 수 있는) 부가적인 폼을 가지고 있는 온라인 마법사처럼 특별한 경우나 사용자가 폼을 취소했을 경우에 사용할 수 있다.

두 번째로 남겨두었던 질문은 검증 메서드가 폼을 구성하는 빈의 일부일 때 그 값을 왜 컴포넌트 연관으로 검증해야 하는지에 대한 것이었고 JSF 애플리케이션 생명 주기를 참조해 그 문제에 대해 생각해보라고 했다.

여기에 숨겨진 비밀은, validateEmail 인라인 검증 메서드가 실제 폼을 구성하는 빈의 일부라도, 이 메서드는 반드시 로컬 속성에 직접 전근하는 게 아니라 컴포넌트 연관을 통해 접근해야 한다는 것이다. 컴포넌트 값이 (모델 값 수정하기 단계에서) 모델이 묶이기 전에 검증이 이뤄지기 때문이다. 모델은 아직 아무것도 모르는 상태다. 따라서 인라인 커스텀 검증 로직을 마치 커스텀 Validator 객체에 있는 검증인 것처럼 사용해야 한다. 또한 이런 이유 때문에 메서드 시그너처가 똑같아야 한다는 것이다.

이 두 가지 남겨두었던 과제를 해결하면서 흥미로웠던 사실은 이 둘 모두 JSF 애플리케이션 생명 주기를 기반으로 하고 있다는 것이다. 둘 모두 생명 주기를 독자들이 원하는 대로 조작할 수 있을 만큼 이해하는 것이 얼마나 중요한지 암시하고 있다.


결론

지금까지 본 기사를 통해 JSF 타입 전환과 검증을 살펴봤다. 사실 (최소한 JSF 버전의) 애플리케이션에서 이것들을 처리할 때 필요한 거의 대부분을 살펴본 것이다!

물론, 전부다 살펴본 것은 아니다. 가령, MaFaces(참고자료 참조)를 보면 여기서 논의하지 않은 것이나 JSF가 제공하지 않는 검증기를 제공한다. 추가적으로, 가장 자주 사용하는 타입 전환과 검증 기술에 대해 논의했지만, 여기서 살펴보지 않은 것들도 있음을 알아두자. 예를 들어, 커스텀 컴포넌트를 작성할 때 타입 전환과 검증을 컴포넌트의 인코딩이나 디코딩 처리시에 (컴포넌트나 그것의 기능에 따라) 직접 다룰 수도 있다. 하지만 이런 자세한 내용은 나중에 커스텀 컴포넌트 개발에서 더 자세히 다루겠다.

명심해야 할 것은 타입 전환과 검증이 항상 같이 수행되진 않는다는 것이다. 타입 전환은 문자열을 객체로 바꾼다. 하지만 대부분의 검증기는 문자열에 적용을 한다. 따라서 커스텀 컨버터와 검증기를 함께 사용할 때 유의해야 한다. 예를 들어, PhoneNumber 객체는 길이 검증기와 같이 사용할 수 없다. 이 경우, 반드시 커스텀 검증기를 만들거나 특정 검증 로직을 커스텀 컨버터에 삽입할 수 있겠다. 보통 후자를 선택하는데 그렇게 하면 커스텀 컨버터를 (그 안에 내장된 검증 로직으로) 특정 객체 타입과 연관 지을 수 있으며 JSF가 해당 객체 타입을 다룰 것이기 때문이다. JSF는 컨버터의 id를 JSP에 기술하지 않아도 자동으로 해줄 것이다(물론 이를 게으른 프로그래밍이라고 할지도 모르곘다. 따라서 모든 경우에 적합한 솔루션이고 보기엔 어려울 것이다).

이 기사에서 우리가 논의한 것을 살펴보면 JSF는 유연하고, 강력하고, 확장성이 좋은 웹 애플리케이션 개발 프레임워크다. 표준 컨버터와 검증기에 더해, JSF는 커스텀 구현체를 애플리케이션과 프레임워크 개발자들이 선호하는 형태로 사용할 수 있게 한다. 최종적으로, 타입 전환과 검증 전략은 독자들에게 달렸다. JSF를 이용해 (표준 타입 전환과 검증기 그리고 인라인 검증을 통해) 쉽고 빠르게 프로토타입을 만들 수 있으며, 따라서 이후 개발 과정에서 더 복잡한 제품 솔루션을 진행할 수 있다. 그리고 이 전부가, JSF 애플리케이션 생명 주기가 일관적으로 데이터 모델 무결성을 보장할 수 있도록 신뢰할 만한 기반 시설을 제공하는 덕분이다.

다음 회에는 JSF 커스텀 컴포넌트 작성에 대해 더 자세히 살펴보겠다.



다운로드 하십시오

설명이름크기다운로드 방식
JAR 파일 없는 validation.zip 파일jsf-validation-no-jars.zip32 KBHTTP
JAR 파일 있는 validation.zip 파일jsf-validation.zip2170 KBHTTP

다운로드 방식에 대한 정보


참고자료

필자소개

Rick Hightower는 ArcMind Inc.의 CTO로 일한다. J2EE 개발에 XP를 적용하는 것을 다룬 Java Tools for Extreme ProgrammingProfessional Struts의 공동 저자이기도 하다.

Paul Tabor는 Rizon Software의 핵심 기술 부서를 담당하고 있다. Paul Tabor는 지난 몇 년간 Rick Hightower와 몇 번의 프로젝트를 함께 했다. Paul과 Rick은 JSF를 스프링, 하이버네이트와 함께 몇몇 기술 난도가 높은 애플리케이션에서 편하게 사용할 수 있도록 하고 있다. Paul은 ptabor@rizonsoftware.com 또는 ptabor@arc-mind.com을 통해 연락할 수 있다.

잘못된 도움말 신고

부정사용 신고

감사합니다. 이 항목은 운영자가 관심을 표시했습니다.


잘못된 도움말 신고

부정사용 신고

제출실패 신고. 나중에 다시 실행해주세요.


디벨로퍼웍스 로그인


IBM ID가 필요하세요?
IBM ID를 잊으셨습니까?


비밀번호를 잊으셨습니까?
비밀번호 변경

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관.

 


developerWorks에 처음 로그인하면 developerWorks프로파일이 생성됩니다.귀하의 프로파일에서 동의하신 내용이 공개되지만 이 사항은 언제든지 변경 가능합니다. 귀하의 성명(숨김으로 체크되어 있어도 표시됩니다)과 디스플레이 이름은 게시한 컨텐츠나 사이트 엑세스시 표시됩니다.

화면상에 보여지는 닉네임을 정하세요.

처음 developerWorks에 로그인할 때 프로파일이 작성되므로, 이를 위해 디스플레이 이름을 선택해야 합니다. 선택하신 디스플레이 이름은 developerWorks에 게시한 컨텐츠에 표시됩니다.

3글자 이상 31글자 이하의 길이로 사용 가능합니다. dW커뮤니티 내에서는 보안상 이메일주소를 제외한 다른 이름을 지정하셔야 합니다.

3개의 &이나 대쉬를 포함해주시고 31글자내로 제한해주세요.


developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관.

 


아티클 순위

의견

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=20
Zone=자바
ArticleID=340196
ArticleTitle=불신자를 위한 JSF: JSF 타입 전환과 검증
publish-date=09232008
author1-email=rhightower@arc-mind.com
author1-email-cc=jaloi@us.ibm.com
author2-email=ptabor@rizonsoftware.com
author2-email-cc=jaloi@us.ibm.com

태그

Help
검색 필드를 사용하여 My developerWorks 내에서 해당 태그가 사용된 모든 종류의 컨텐츠를 검색하십시오.

태그를 더 많이 보거나 적게 보기 위해 슬라이더 막대를 사용하십시오.

인기 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 최고 인기 태그를 보여줍니다.

내 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 귀하의 태그를 보여줍니다.

검색 필드를 사용하여 My developerWorks 내에서 해당 태그가 사용된 모든 종류의 컨텐츠를 검색하십시오. 인기 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 최고 인기 태그를 보여줍니다. 내 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 귀하의 태그를 보여줍니다.