Validator에도 여러 가지가 있네

JSF 스캐(스크린캐스팅)를 찍다가 알게 된건데, 기본 검증기, Application-level 검증, 커스텀 검증기, 표준 검증기가 있었습니다.

기본 검증기는 프레임워크에서 제공하는 것으로 JSF에는 길이 검사라던가, 숫자가 최소치와 최대치 사이의 값인지 범위를 검사할 수 있는 기본 Validator들을 제공하고 있었습니다. 스프링 MVC는 어떨까요? 글쎄요. 없는 것 같네요. ValidationUtils는 있지만 JSF 처럼 간단하게 뷰에서 바로 써먹을 수 있는 건 없는 것 같습니다.

다음은 폼에서 이미 검증을 마쳐서 모델에 사용자가 입력한 데이터를 담은 상태로 비즈니스 로직을 검사하는 검증입니다. 이 검증은 프레임워크에 독립적이며 도메인 클래스가 해당 로직을 가지고 있기도 합니다. 예를 들어.. member.validateBy주민번호() 같은 메소드(주민등록 번호로 성별과 생년 월일이 일치하는지 검사하는 로직)를 만들고 이를 컨트롤러에서 호출한 다음에 예외를 잡아서 화면에 어떤 에러 메시지를 출력하는 방법이 있겠습니다. 비즈니스 로직에 적합한지 검사하려면 역시 화면에 붙어있는 검증기와는 별개로 이런게 필요한 것 같습니다. 스프링MVC에서도 검증 로직이 순전히 도메인 레벨에서 가능하다면 이런 식으로 검증할 수도 있겠습니다.

다음은 검증기는 검증기인데, 특정 인터페이스를 따르지도 않고 완전한 POJO 검증기 입니다. 위에서 애플리케이션 검증 로직을 별도의 클래스로 빼내고 그 로직을 뷰에서 값을 입력 받을 때 호출하게 하는 건데, JSF의 EL은 메소드 호출도 정말 간단하게 할 수 있더군요. 그냥 이런 검증기를 managed-bean으로 등록하고 그 로직을 호출하면 됐습니다. 검증 로직을 별도의 클래스로 분리하고 한 곳에서 관리할 때 좋겠습니다.

마지막은 스프링이 제공하는 Validator인터페이스 처럼 프레임워크의 틀에 맞는 검증기를 구현하고 그것을 설정하여 사용하는 겁니다. JSF에도 그런 구조가 갖춰져 있죠. 이 걸 사용하는 장점은 어떤 틀이 있기 때문에 위처럼 중구 난방으로 작성할 수 있는 코드에 규약을 정하고 그걸 따르는 검증기를 공유할 수 있다는 겁니다. JSF는 스프링 @MVC와 달리 이 검증 로직 실행을 프레임워크의 Phase 중에서 Validation Phase에 하기 때문에 이 검증 로직을 만족하지 못하면 모델에 값이 들어갈 수가 없습니다. 스프링은 아니죠. 일단 모델에 값을 받은 상태에서 검증을 하는거죠.

흠~~ 그래서 결론적으로 생각해볼 때, 여러 검증 단계와 방법을 제공하는 건 JSF가 더 좋아보이지만, 모델에 값을 설정한 상태에서 Validator라는 규격을 가지고 그 안에서 비즈니스 로직 검사도 수행(application-level 검증)도 할 수 있는.. 스프링 MVC의 단순한 구조가 오히려 개발을 더 깔끔하게 할 수 있게 해주지 않나 생각합니다. 모든 개발자가 검증 로직을 사방한대서 한다고 생각하면.. 어휴.. 끔찍하죠.ㅋㅋ

Declarative Validators – Valang 사용하기

XML 설정파일로 Validation을 할 수 있습니다.


Jakarta Commons Validator또는 Valang(Validation language)을 사용하여 선언적으로 Validation을 할 수 있습니다. 여기서는 SpringMVC 9장에서 소개하고 있는 Valang을 살펴봅니다.

1. 필요한 jar파일
Spring Modules

2. 사용법
– org.springmodules.validation.ValangValidatorFactoryBean 사용하여 validation bean만들기
– syntax 등록
– custom function 등록
– Validation사용할 수 있는 Controller에 Setter Injection 시키면 끝!

3. 예제코드



<bean id=“caseSwappingValidator”


       class=“org.springmodules.validation.ValangValidatorFactoryBean”>


       <property name=“syntax”>


             <value>


                    <![CDATA[


{ name : alterCase(?) = ‘sTEVEN’ : ‘Name must be Steven’ }


]]>


             </value>


       </property>


       <property name=“customFunctions”>


             <map>


                    <entry key=“alterCase”


       value=“com.apress.expertspringmvc.validation.AlterCaseFunction” />


             </map>


       </property>


</bean>

Vlidator – Property 파일 사용하기

이전 글에서 사용한 방법으로는 입력 필드에 값이 비어있는지만 확인할 수 있습니다. 아마도 Errors 객체를 사용할 때 값이 비어있는지 검사하려면 if 문이 자주 사용되니까 그 코드를 줄여주기 위해 만든것 같습니다.

따라서 빈 값인지 확인할 때는 유용하게 사용할 수 있지만 그 이외의 경우에는 Errors 인터페이스를 사용해야 한다.
사용자 삽입 이미지Errors의 인터페이스 중에 에러 메시지를 기록할 수 있는 메소드들입니다.

인자의 종류를 살펴보면 다음과 같습니다.

field :: 에러 메시지를 남길 대상이 되는 필드
errorCode :: 프로퍼티 파일에 있는 에러 메시지의 이름(키)
errorAgs :: 프로퍼티 파일에 있는 에러 메시지의 내용(값)을 출력할 때 특정 문자열을 넘겨 줄 수 있습니다. 그 때 이 아규먼트를 사용합니다.
defaultMessage :: 프로퍼티 파일에서 errorCode에 해당하는 메시지 키를 못찾으면 여기에 입력한 값을 출력합니다.

프로퍼티 파일을 사용하는 방법은 간단합니다.

    <bean id=”messageSource” class=”org.springframework.context.support.ResourceBundleMessageSource”>
        <property name=”basename” value=”message” />
    </bean>

위와 같이 messageSource를 등록합니다. 이 때 프로퍼티 파일의 이름을 basename에 입력해 줍니다. 만약 프로퍼티 파일이 여러개라면 besenames 속성을 사용하여 다음과 같이 list로 넘겨줍니다.

<beans>
  <bean id=”messageSource”
        class=”org.springframework.context.support.ResourceBundleMessageSource”>
    <property name=”basenames”>
      <list>
        <value>format</value>
        <value>exceptions</value>
        <value>windows</value>
      </list>
    </property>
  </bean>
</beans>

그 다음 프로퍼티 파일을 작성합니다. 위 설정에서 message 라는 이름을 입력했으니까 프로퍼티 파일은 message.properties 또는 message_ko_KR.properties 이런식으로 입력값 뒤에 지역코드가 붙은 프로퍼티 파일도 자동으로 읽히게 됩니다.

required=Input {0} Properties
passwordTooShort=Enter password at least 6 characters.
notSamePassword=Enter the same vlaue with password

프로퍼티에 저렇게 내용을 채우고 이제는 Validator를 만듭니다. 저번에 만든 코드에서 살짝 내용을 추가/수정했습니다.

public class MemberInfoValidator implements Validator{

    public boolean supports(Class clazz) {
        return MemberInfo.class.isAssignableFrom(clazz);
    }

    public void validate(Object object, Errors errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, “email”, “required”, new Object [] {“email”}, “Enter your email”);
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, “password”, “required”, new Object [] {“password”}, “Enter your password”);
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, “confirmPassword”, “required”, new Object [] {“confirmPassword”}, “Enter the same password for confirmation”);
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, “confirmMember”, “required”, new Object [] {“confirmMember”}, “Enter ajn member code”);
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, “name”, “required”, new Object [] {“name”}, “Enter your name”);

        MemberInfo memberInfo = (MemberInfo)object;
        if(memberInfo.getPassword().length() < 6)
            errors.rejectValue(“password”, “passwordTooShort”);

        if(!memberInfo.getPassword().equals(memberInfo.getConfirmPassword()))
            errors.rejectValue(“confirmPassword”, “notSamePassword”);
    }

}

이제 실행해보면 다음과 같이 에러메시지들이 출력됩니다.
사용자 삽입 이미지

Vlidator – ValidationUtils 사용하기

Spring에서 Validator를 구현하는 방법은 두 가지가 있습니다.
1. Programmatic
2. Declarative

그 중에서 첫 번째 Programmatic 방법을 사용하여 구현할 때 ValidationUtils를 사용하면 매우 간단하게 구현할 수 있습니다.
사용자 삽입 이미지인터페이스 중에 인자가 네개인 녀석을 사용하여 defaultMessage를 주면 프로퍼티 파일을 만들지 않아도 메시지를 출력할 수 있습니다.

1. Validator 만들기

public class MemberInfoValidator implements Validator{

    public boolean supports(Class clazz) {
        return MemberInfo.class.isAssignableFrom(clazz);
    }

    public void validate(Object object, Errors errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, “email”, “required”, “Enter your email”);
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, “password”, “required”, “Enter your password”);
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, “confirmPassword”, “required”, “Enter the same password for confirmation”);
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, “confirmMember”, “required”, “Enter ajn member code”);
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, “name”, “required”, “Enter your name”);
    }

}

Validator 인터페이스를 구현하고 ValidationUtils를 사용하여 간단하게 구현할 수 있습니다.

2. Controller에 등록하기

  public CreateMemberInfoController() {
        setCommandClass(MemberInfo.class);
        setCommandName(“memberInfo”);
        setFormView(“createMemberInfo”);
        setSuccessView(“viewMemberList”);
        setValidator(new MemberInfoValidator());
    }

setValidator 메소드를 사용하여 위에서 만든 Validator를 등록해 줍니다.

3. 화면에 보여주기

<form:errors path=”속성 이름” />

이런식으로 속성 이름을 적어주면 그 이름에 해당하는 에러 메시지를 고자리에 출력해 줍니다.

사용자 삽입 이미지

<form:errors path=”*” />

이렇게 써주면 저 태그가 들어간 위치에 모든 에러 메시지를 모아서 출력할 수 있습니다. 좋군요~
사용자 삽입 이미지

Fail-fast vs. complete validation

원문 : http://hanuska.blogspot.com/2007/04/fail-fast-vs-complete-validation.html
번역[footnote]해도 되냐고 물어보긴 했는데 아직 댓글이 안뜨네요. 일단 올리고 안 된다고 하면 지우죠 뭐;[/footnote]

대부분 모든 어플리케이션은 사람 또는 다른 어플리케이션과의 상호 작용을 통한 데이터를 가지고 작업해야 합니다. 하지만 종종 이런 데이터들은 어플리케이션이 수용할 만한 요구 사항을 충족하지 못할 수 있습니다. 따라서 데이터는 반드시 검증해야 합니다.

Validation이란 무엇인가?

입력되는 데이터는 그 다음 처리 과정에 적합하여 유효한 것임을 판별하기 위해 반드시 일련의 검증 과정을 거쳐야 합니다.

예를 들어 세개의 멤버를 가지고 있는 클래스르 생각해 봅시다. name:String, created:Date total:int. name은 반드시 설정되어야 하며 길이가 최소한 세 글자 이상이여야 한다는 요구사항을 가지고 있습니다. created 역시 필수 요소이며 현재 시각 이전이어야 합니다. total은 음의 정수면 안됩니다.

데이터 유효성 검증에는 보통 두 가지 접근 방법이 있습니다. fail-fast validation 과 complete validation 입니다.

Fail-fast validation

How it works
만약 어떤 검증 규칙(validation rules)가 실패하면 검증은 그 즉시 멈추고 데이터가 유효하지 않다는 것을 알려주고 처리 과정을 중단합니다.

Output
검증의 결과에 대한 Boolean 값을 알려줍니다. true for valid, false for invalid.

Pros
검증 실패가 발생하면 그 후에 수행할 검증 작업을 멈추기 때문에 complete validation 보다 빠릅니다. 리포팅에 대한 오버헤드가 없습니다.

Cons
검증 실패에 관한 충분한 정보를 제공하지 못합니다.

When to use it
왜 검증이 실패 했는가 하는 자세한 정보가 필요하지 않고 단순히 true 나 false같은 결과를 원할 때 적당합니다. 보통 수정할 수 없는 데이터를 받아 들일 때(사람이 입력하는 것이 없는 시스템에서) 적당합니다.

Example
boolean isValid(String name, Date created, int total)
데이터가 들어가면 Boolean 값을 리턴합니다.

Complete validation



How it works
검증 규칙이 실패 하더라도 검증 절차를 중단하지 않습니다. 데이터가 유효하지 음을 체크 하고 전체 검증 절차를 마친 후에 다음에 처리할 작업을 중단합니다.

Output
Boolean 값으로 입력한 데이터가 유효한지 알려줍니다. true for valid, false for invalid. 에러 컬렉션 같은 폼에 유효성 실패에 대한 정보를 보관합니다.

예들 들어 Struts 프레임워크의 ActionMessages 클래스나 Atlassian JIRA의 ErrorCollection를 참고 하세요.[footnote]Spring의 Errors 클래스도 예가 될 수 있을 것 같네요.[/footnote]

Pros
유효성 실패의 원인에 대한 정보를 제공합니다. 입력하는 데이터를 수정하는데 필요한 피드백을 제공할 수 있습니다.

Cons
모든 검증 절차를 수행하며 검증에 대한 결과에 대한 추가적인 정보를 리포팅 하기 때문에 file-fast validation보다 느립니다.

When to use it
검증 실패에 대한 원인이 필요할 때 적당합니다. 이 정보는 사용자가 데이터를 입력하고 어떻게 수정해야 하는지에 대한 힌트를 제공합니다.

Example
데이터는 에러 컬렉션과 함께 전달합니다. 유효하지 않은 데이터에 대한 정보는 에러 컬렉션으로부터 알 수 있기 때문에 메소드는 아무것도 반환하지 않습니다.

Conclusion
만약 복잡한 검증 규칙을 입력 필드 단위로 쪼갤 수 있다면 (JavaScript나 AJAX를 사용하여)사용자가 시스템에 적응 하도록 할 수 있습니다. 실시간으로 입력한 데이터에 대한 피드백을 받을 수 있기 때문이죠.

또한 여러 입력 값들을 가지고 데이터의 유효성을 판단해야 하는 경우를 생각해 봅시다. 세 개의 입력 필드로 구성된 하나의 날짜를 나타내는 값이 그런 예에 해당합니다(실제로 이렇게 하진 마세요. 데이터를 입력 받는 좋지 않은 방법입니다.).

사용자 삽입 이미지
또는 아래처럼 Other를 선택했을 때 text area가 보여야 하는 것처럼 조건적으로 필요한 필드의 경우를 생각할 수 있습니다.
 사용자 삽입 이미지
두 유형의 검증 방법 모두 각각의 목적이 있습니다. 검증이 실패 했을 때 수정하거나 판단하기 위해 여러분에게 얼만큼의 정보가 필요한지에 따라 적당한 방법을 사용하면 됩니다.