Spring - Validation

SPRING 2014. 3. 25. 06:16
728x90
반응형

13.Validation

Spring에서는 사용자가 입력한 값에 대한 유효성을 체크하기 위해 Spring Validator 또는 JSR-303 Validator를 사용할 수 있도록 지원하고 있다.

13.1.Spring Validator

Spring MVC에서는 Spring Validator를 이용하여 입력 필드의 값에 대해 Validation Check를 수행하고 Errors 객체를 통해 에러 메시지를 출력해 줄 수 있도록 지원한다. 또한 Errors 객체에 담겨진 에러 메시지는 jsp 페이지에서 form:errors 태그를 통해 출력될 수 있다.

13.1.1.Validator 생성

  • ValidatorUtils 사용

    필수 입력 필드에 대해 Validation Check를 수행하고 에러 메시지를 출력할 수 있도록 지원한다. 이것은 ValidatorUtils를 사용하여 간단히 구현할 수 있다. 다음은 Validator 인터페이스를 구현한 UserValidator.java 클래스의 일부이다.

    public class UserValidator implements Validator {
        public boolean supports(Class clazz) {
            return UserVO.class.isAssignableFrom(clazz);
        }
    			
        public void validate(Object object, Errors errors) {
            // validationUtils를 이용하여 입력값이 비었는지 체크
              ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userName",
                "required", new Object[] { "userName" }, "Enter your name");
            ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password",
                "required", new Object[] { "password" }, "Enter your password");
  • Errors 사용

    Validation Check 결과 발생된 Error를 Errors 객체를 사용하여 저장함으로써 해당 필드에 대해 정의된 에러 메시지를 출력할 수 있도록 지원하며 그 예는 다음과 같다.

    public class UserValidator implements Validator {
        public boolean supports(Class clazz) {
            return HelloVO.class.isAssignableFrom(clazz);
        }
    
        public void validate(Object object, Errors errors) {
            
            HelloVO helloVO = (HelloVO) object;
            if (helloVO.getPassword().length() < 6)
                errors.rejectValue("password", "error.password.tooshort");
    
            if (!helloVO.getPassword().equals(helloVO.getConfirmPassword()))
                errors.rejectValue("confirmPassword", "error.confirm");
        }
    }

    Validation Error가 있는 경우 메시지 리소스 파일에 미리 정의된 error.password.tooshort, error.confirm 등의 메시지가 출력될 것이다.

13.1.2.Validator 활용

생성한 Validator를 활용하기 위해서는 해당 Validator를 Inject하여 사용하거나 Controller 클래스 내에 @InitBinder 메소드를 정의하고 해당 메소드의 입력 인자로 전달된 Binder에 해당 Validator를 셋팅하여 활용할 수 있다.

13.1.3.<form:errors> 태그 사용

Validation Error를 JSP 페이지에서 쉽게 출력하기 위해 Spring MVC에서 제공하는 form 태그 중 <form:errors> 태그를 사용할 수 있다. 이 태그를 사용하기 위해서는 다음과 같은 절차를 따르도록 한다.

  • 태그 라이브러리 등록

    Spring form 태그 라이브러리를 사용하기 위해서는 spring-form.tld 파일이 필요하며 이는 spring-webmvc-x.x.x.jar 파일에 포함되어 있다. 이 form 태그를 사용하기 위해서는 JSP 페이지에 다음과 같이 taglib 정의가 추가되어야 한다.

    <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
  • <form:form> 태그 사용

    form 태그를 사용하려면 commandName 속성을 지정해야 하는데 이 이름은 JSP 페이지에서 사용되는 commandName과 일치해야 하며 commandClass와 같은 타입의 객체이어야 한다. commandName에 특정 이름을 부여하지 않으면 기본 값은 command로 셋팅된다. form 태그는 여러가지 폼 입력 태그들을 갖는다. 그 중, Validation Error 표현을 위한 태그는 <form:errors>이며 이 태그는 속성으로 path를 가진다. path 값으로 "*" 값을 주게 되면 commandClass가 가지는 모든 속성에 대한 Error 메시지를 출력하게 된다. 다음은 <form:errors> 태그가 정의되어 있는 getUser.jsp 파일의 일부이다.

    <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
    <tr>
        <td> Name :</td>
        <td><form:input path="userName" />(required)</td>
        <td><form:errors path="userName" /></td>
    </tr>
    <tr>
        <td>password :</td>
        <td><form:password path="password" />(required, 6자이상입력)</td>
        <td><form:errors path="password" /></td>
    </tr>

13.2.Spring 3 Validation

Spring 3 이후부터는 Bean Validation에 대한 표준을 정의한 JSR-303 Spec.을 지원하고 있다. Validation은 선언적인 형태와 프로그램적인 형태로 구분할 수 있으며 Hibernate Validator와 같은 JSR-303 Spec.을 구현한 구현체를 연계하여 처리된다.

13.2.1.JSR-303 (Bean Validation) Basic

JSR-303은 Bean Validation을 위한 표준을 정의하고 있으며 특정 어플리케이션을 구성하는 도메인 클래스에 대해 JSR-303 Annotation을 활용하여 Validation Constraints를 부여하게 되면 런타임시에 이를 기준으로 Validation Check가 이루어지게 된다. 다음은 JSR-303 Spec.에서 제시한 Annotation 목록이다.

Annotation Supported Type

Description

@AssertFalse boolean, Boolean 해당 속성의 값이 false인지 체크한다.
@AssertTrue boolean, Boolean 해당 속성의 값이 true인지 체크한다.
@DecimalMax

BigDecimal, BigInteger, String, byte, short, 

int, long and primitive type에 대한 wrappers

해당 속성이 가질 수 있는 최대값을 체크한다.
@DecimalMin BigDecimal, BigInteger, String, byte, short,
int, long and primitive type에 대한 wrappers
해당 속성이 가질 수 있는 최소값을 체크한다.
@Digits BigDecimal, BigInteger, String, byte, short,
int, long and primitive type에 대한 wrappers
해당 속성이 가질 수 있는 정수부의 자리수와
소수부의 자리수를 체크한다.
@Future java.util.Date, java.util.Calendar 해당 속성의 값이 현재일 이후인지 체크한다.
@Max BigDecimal, BigInteger, String, byte, short,
int, long and primitive type에 대한 wrappers
해당 속성이 가질 수 있는 최대 Length를 체크한다.
@Min BigDecimal, BigInteger, String, byte, short,
 int, long and primitive type에 대한 wrappers
해당 속성이 가질 수 있는 최소 Length를 체크한다.
@NotNull any type 해당 속성의 값이 Null이 아닌지 체크한다.
@Null any type 해당 속성의 값이 Null인지 체크한다.
@Past java.util.Date, java.util.Calendar 해당 속성의 값이 현재일 이전인지 체크한다.
@Pattern String 해당 속성의 값이 정의된 Regular Expression에
부합하는지 체크한다. Regular Expression은
Java Regular Expression Convention
(see java.util.regex.Pattern)에 맞게 정의해야 한다.
@Size String, Collection, Map and arrays 해당 속성이 가질 수 있는 최대, 최소 Length를 체크한다.
@Valid any non primitive type 해당 객체에 대해 Validation Check가 이루어진다.


이 외에도 JSR-303 구현체별로 Validation Constraint 정의를 위한 Custom Annotation을 추가로 제공하기도 한다.

다음은 Foundation Plugin 설치로 추가된 도메인 클래스 ~/domain/Movie.java의 일부로써 앞서 언급한 JSR-303 Annotation을 활용하여 Validation Constraint를 정의하고 있다. 예를 들어, title 속성은 Null 값을 가질 수 없으며 최소 1자리, 최대 50자리까지만 허용하며 runtime 속성값은 최대 180을 초과할 수 없고 정수부 3자리 소수부는 0자리를 허용하고 있음을 알 수 있다.

public class Movie implements Serializable {

    private static final long serialVersionUID = 1L;
	
    private String movieId;

    @NotNull
    @Size(min = 1, max = 50)
    private String title = "";
	
    @NotNull
    @Size(min = 1, max = 50)
    private String director;

    private Genre genre;

    @NotNull
    @Size(min = 5, max = 100)
    private String actors;

    @DecimalMax(value = "180")
    @Digits(integer=3, fraction=0)
    private int runtime;

    @DateTimeFormat(iso = ISO.DATE)
    @Future
    private Date releaseDate;

    @NumberFormat(pattern = "#,###")
    @Digits(integer=5, fraction=0)
    private int ticketPrice;

    private String posterFile;

    private String nowPlaying = "Y";

    // getter, setter ...
}

13.2.2.JSR-303 (Bean Validation) Optional

JSR-303 Spec.을 준수하는 모든 Annotation은 Annotation별 속성 외에 payload, groups, message라는 속성을 공통적으로 가진다. 각 속성이 가지는 의미에 대해 살펴보도록 하자.

  • payload (Programmatic Validating의 경우 활용 가능) : 사용된 Validation Constraint와 관련된 메타 정보를 정의하는데 사용된다. 특정 Constraint에 대해 payload 속성의 값으로 심각도를 정의해두면 Validation Error가 발생하였을 경우 심각도 정보를 추출할 수 있게 된다. 다음은 payload 정보가 추가 정의된 도메인 클래스의 일부로 title, director의 Validaion Constraint에 대해 Severity라는 클래스의 Error와 Warning 클래스로써 payload 값을 부여하고 있음을 알 수 있다.

    public class Movie implements Serializable {
    
        private static final long serialVersionUID = 1L;
    
        private String movieId;
    
        @NotNull(payload = Severity.Error.class)
        @Size(min = 1, max = 50, payload = Severity.Warning.class)
        private String title = "";
    
        @NotNull(payload = Severity.Error.class)
        @Size(min = 1, max = 50, payload = Severity.Warning.class)
        private String director;
    
        // ...
    }

    다음은 위에서 언급한 Severity 클래스의 모습이다. 내부에 Warning, Error라는 클래스 정의를 포함하고 있으며 이들 각각은 javax.validation.Payload를 상속받고 있음에 유의해야 한다.

    public class Severity {
        public static interface Warning extends Payload {
        };
    
        public static interface Error extends Payload {
        };
    }

    위와 같이 코드가 구성된 경우 Validation Error를 담고 있는 ConstraintViolation 객체의 getConstraintDescriptor().getPayload() 메소드를 호출함으로써 Payload 정보를 추출할 수 있다.

    Set<ConstraintViolation<Movie>> constraintViolations = validator.validate(movie);
    System.out.println("the number of constraint violation is "	+ constraintViolations.size());
    
    Iterator<ConstraintViolation<Movie>> iterator = constraintViolations.iterator();
    
    while (iterator.hasNext()) {
        ConstraintViolation<Movie> constraintViolation = iterator.next();
        Set payloads = constraintViolation.getConstraintDescriptor().getPayload();		
        // ...
    }

    위에서 언급한 payload 샘플 코드는 본 섹션 내의 다운로드 - anyframe.sample.validation. payload를 통해 다운로드받을 수 있다.

  • groups (Programmatic Validating의 경우 활용 가능) : 사용된 Validation Constraint의 그룹 정보를 정의하는데 사용된다. 일부 Constraint에 대해 동일한 그룹을 부여하게 되면 특정 그룹에 대해서만 Validation 작업을 수행할 수 있게 된다. 예를 들어, 특정 도메인 객체가 생성되는 시점의 Validation Check 대상 속성들과 해당 도메인 객체가 수정되는 시점의 Validation Check 대상 속성들이 다를 수 있기 때문에 이들에 대해 그룹을 부여하고 그룹별로 Validation을 수행하고자 하는 경우 활용할 수 있다. 다음은 groups 정보가 추가 정의된 도메인 클래스의 일부로 title, director, actors에 대해서는 Draft, Playing이라는 그룹을 부여하고 runtime, releaseDate, ticketPrice에 대해서는 Playing이라는 그룹만 부여하고 있음을 알 수 있다.

    public class Movie implements Serializable {
        private static final long serialVersionUID = 1L;
    
        private String movieId;
    
        @NotNull(groups = { Draft.class, Playing.class })
        @Size(min = 1, max = 50, groups = { Draft.class, Playing.class })
        private String title = "";
    
        @NotNull(groups = { Draft.class, Playing.class })
        @Size(min = 1, max = 50, groups = { Draft.class, Playing.class })
        private String director;
    
        @NotNull(groups = { Draft.class, Playing.class })
        @Size(min = 5, max = 100, groups = { Draft.class, Playing.class })
        private String actors;
    
        @DecimalMax(value = "180", groups = Playing.class)
        @Digits(integer = 3, fraction = 0, groups = Playing.class)
        private int runtime;
    
        @Future(groups = Playing.class)
        private Date releaseDate;
    
        @Digits(integer = 5, fraction = 0, groups = Playing.class)
        private int ticketPrice;
    	
        // ...
    }

    즉, 영화가 등록될 당시(Draft Group)에는 title, director, actors에 대해서만 Validation Check가 이루어지고 영화 상영이 결정된 이후(Playing Group)부터는 runtime, releaseDate, ticketPrice에 대해서도 추가적으로 Validation Check가 이루어질 수 있도록 하기 위함이다.

    다음은 위에서 groups 정의시 활용한 Draft.java 클래스의 모습이다. group 클래스는 javax.validation.groups.Default 유형이어야 하며, group 클래스 사이에서 계층 관계를 가질 수 있다. 그리고 하위 계층 그룹에 대해 Validation Check 요청이 있을 경우 상위 계층에 대한 Validation Check도 함께 이루어지게 된다. groups 속성값이 정의되지 않은 경우 Default group으로 간주된다.

    public interface Draft extends Default {
    }

    위와 같이 코드가 구성된 경우 Validator의 validate() 메소드 호출시 group 정보를 인자로 전달하면 해당 group에 속한 속성 정보에 대해서만 Validation Check가 수행된다.

    Set<ConstraintViolation<Movie>> constraintViolations = validator.validate(movie, Draft.class);
    System.out.println("the number of constraint violation is "	+ constraintViolations.size());
    
    Iterator<ConstraintViolation<Movie>> iterator = constraintViolations.iterator();
    
    while (iterator.hasNext()) {
        ConstraintViolation<Movie> constraintViolation = iterator.next();	
        // ...
    }

    위에서 언급한 groups 샘플 코드는 본 섹션 내의 다운로드 - anyframe.sample.validation. groups를 통해 다운로드받을 수 있다.

  • message : Validation Error가 발생하였을 경우 표현되는 메시지를 정의하는데 사용된다. 기본적으로 사용중인 Validator를 포함하는 라이브러리 내에 포함된 메시지 리소스 파일로부터 해당 Annotation의 {fully-qualified class name}.message에 해당하는 메시지 값을 추출하게 된다. 예를 들어, @NotNull Check시 에러가 발생하면 javax.validation.constraints.NotNull.message에 해당하는 메시지가 표현될 것이다. 기본적으로 제공되는 메시지가 아닌 다른 메시지를 표현해주고 싶을 경우에는 message의 속성값으로 신규 메시지 key를 정의하면 된다. 그리고 클래스패스 상위에 해당 key와 이에 대한 메시지를 포함하고 있는 메시지 리소스 파일을 정의한다.

    메시지 리소스 파일에 대해서는 기본적으로 국제화가 지원되며, Hibernate Validator의 경우 기본적으로 영어,불어,독일어 형태의 메시지 리소스 번들을 제공하고 있는데 만일 다른 언어로 구성된 메시지 리소스 파일을 추가하고자 원한다면 클래스패스 내에 org/hibernate/validator/ValidationMessages_{locale}.properties 파일을 추가하고 JSR-303 Annotation 각각에 대한 메시지를 정의하도록 한다.

13.2.3.Custom Constraints

JSR-303에서 기본적으로 제공하는 Annotation만으로 특정 도메인 클래스의 속성값에 대한 Validation Check가 수행되기 어려운 경우 프로젝트에 적합한 Custom Constraints를 정의할 수 있다. Custom Constraints를 활용하기 위해서는 Custom Annotation과 Custom Validator 구현이 이루어져야 한다. 다음은 전화번호 속성에 대한 Validation Check를 위해 신규 정의한 Telephone.java 클래스의 일부이다.

@Target( { ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = TelephoneValidator.class)
@Size(min = 12, max = 13)
public @interface Telephone {
    String message() default "{anyframe.sample.validation.constraint.Telephone.message}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

위 코드에 의하면 @Telephone은 Method, Field에 대해 정의 가능하며 런타임시에 적용된다. 그리고 도메인 클래스 내에 @Telephone이 부여된 속성을 만나면 TelephoneValidator가 초기화되어 Validation Check를 수행할 것이다. 또한 @Size Annotation 정의가 추가되어 있어서 @Telephone은 기본적으로 Size에 대해서도 제약하게 된다. @Telephone은 JSR-303 Spec.에서 정의한 기본 속성(message, groups, payload) 외에 추가 속성을 포함하고 있지는 않다.

message 속성의 경우 기본값을 anyframe.sample.validation.constraint.Telephone.message으로 정의하고 있으므로 @Telephone에 대한 Validation Check 관련 Error가 발생한 경우 클래스패스 최상위의 ValidationMessages.properties 파일로부터 anyframe.sample.validation.constraint.Telephone.message을 key로 하는 메시지가 출력될 것이다. 다음은 ValidationMessages.properties 파일의 내용이다.

anyframe.sample.validation.constraint.Telephone.message=must match "0000-000(or 0000)-0000" (max 13)

다음은 @Telephone Annotation에 대해 Validation Check를 수행할 TelephoneValidator.java 파일의 일부이다. 다음 코드에서와 같이 Custom Validator는 javax.validation.ConstraintValidator 인터페이스를 implements해야 하며 Validation Check 로직을 수행할 isValid()라는 메소드를 구현해주어야 한다.

public class TelephoneValidator implements ConstraintValidator<Telephone, String> {
    private java.util.regex.Pattern pattern = java.util.regex.Pattern
        .compile("^[0-9]\\d{2}-(\\d{3}|\\d{4})-\\d{4}$");

    public void initialize(Telephone annotation) {
    }

    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null || value.length() == 0) {
            return true;
        }
        Matcher m = pattern.matcher(value);
        return m.matches();
    }
}

TelephoneValidator는 Regular Expression을 이용하여 전화번호에 대한 패턴을 정의해 두고 이 패턴과 동일하지 않을 경우 Validation Error를 발생하게 된다.

다음은 앞서 정의한 @Telephone 정의를 포함하고 있는 도메인 클래스 Movie.java 파일의 일부이다.

public class Movie implements Serializable {

    private static final long serialVersionUID = 1L;
    
    private String movieId;

    @Telephone
    private String telephone;			

    // ...
}

위에서 언급한 Custom Constraint 샘플 코드는 본 섹션 내의 다운로드 - anyframe.sample.validation. custom를 통해 다운로드받을 수 있다.

13.2.4.Declarative Validating

Spring MVC 2.5 이전에서는 앞서 언급한 바와 같이 Spring Validator를 구현하고 이를 특정 Controller의 Validator로 직접 지정해 주어야만 Validation Check가 이루어졌었다. 그러나 Spring 3 이후부터는 Controller 메소드의 입력 인자에 대해 @Valid라는 Annotation을 부여함으로써 해당 메소드 호출 전에 자동적으로 Validation Check가 이루어질 수 있도록 지원한다. 다음은 Foundation Plugin 설치로 추가된 ~/foundation/moviefinder/web/MovieController.java 클래스 내에 정의된 create() 메소드의 일부이다.

@RequestMapping(params = "method=create")
public String create(..., @Valid Movie movie, BindingResult results, ...) throws Exception {

    if (results.hasErrors()) {
        return "foundationViewMovie";
    }
    
    // ...
}

위 메소드의 경우, 사용자의 입력값을 Movie 객체로 매핑할 때 Validation Check가 이루어지게 되고 결과값은 BindingResult 객체에 담겨지게 된다. 따라서 입력 인자로 전달된 BindingResult 객체 내에 Validation Error가 존재하는 경우 입력 화면으로 되돌아가도록 로직을 구성하면 된다.

또한 Spring에서 제공하는 <form:errors>를 활용하면 Validation Error를 입력 화면에 표현해 줄 수 있게 된다. 다음은 Foundation Plugin 설치로 추가된 webapp/WEB-INF/jsp/foundation/moviefinder/movie/form.jsp 파일의 일부로 title, director 필드에 입력된 값이 유효하지 않을 경우 <form:errors>를 이용하여 표현해 줄 수 있도록 정의하고 있음을 알 수 있다.

<tr>
    <td width="150" class="ct_td"><spring:message code="movie.title" />&nbsp;*</td>
    <td bgcolor="D6D6D6" width="1"></td>
    <td class="ct_write01">
        <form:input path="title" cssClass="ct_input_g" cssErrorClass="text medium error" size="40" maxlength="50" />
        <form:errors path="title" cssClass="errors" />
    </td>
</tr>
<tr>
    <td height="1" colspan="3" bgcolor="D6D6D6"></td>
</tr>
<tr>
    <td width="150" class="ct_td"><spring:message code="movie.director" />&nbsp;*</td>
    <td bgcolor="D6D6D6" width="1"></td>
    <td class="ct_write01">
        <form:input path="director" cssClass="ct_input_g" cssErrorClass="text medium error" size="40" maxlength="50" />
        <form:errors path="director" cssClass="errors" />
    </td>
</tr>

끝으로 선언적인 Validation Check를 위해서는 Validator 지정을 위한 속성 정의가 필요하다. Spring에서는 이를 위해 3가지 방법을 제공한다.

  • Spring 3에서 새롭게 선보이는 mvc namespace를 활용하는 것으로 다음과 같이 정의된 경우 클래스패스로부터 Hibernate Validator와 같은 JSR-303 Validator 구현체가 자동으로 검색되어 모든 @Controller에 적용된다.

    <mvc:annotation-driven />
  • Spring 3에서 새롭게 선보이는 mvc namespace를 활용하되 특정 Validator를 지정하는 것으로 지정된 Validator가 모든 @Controller에 적용된다.

    <mvc:annotation-driven validator="..."/>
  • Controller 클래스 내에 @InitBinder 메소드를 정의하고 해당 메소드의 입력 인자로 전달된 Binder에 특정 Validator를 셋팅하는 것으로, 이 경우 셋팅된 Validator가 특정 Controller에만 적용된다.

    @Controller
    public class MovieController {
    
        @InitBinder
        protected void initBinder(WebDataBinder binder) {
            binder.setValidator(new CustomValidator());
        }
        
        // ...
    }

 

13.2.5.Programmatic Validating

Spring에서는 Validation Check가 필요한 경우에 Hibernate Validator와 같은 JSR-303 Validator 구현체를 실행시킬 수 있도록 하기 위해 LocalValidatorFactoryBean 클래스를 제공한다. LocalValidatorFactoryBean은 클래스패스 내에 JSR-303 구현체와 관련된 라이브러리를 검색하여 Validator를 자동으로 검색해주는 역할을 수행한다. 따라서 LocalValidatorFactoryBean을 Bean으로 정의하고 특정 클래스에서 이 Bean을 참조하여 Validation Check를 수행하면 된다.

<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
@Service
public class MovieServiceImpl implements MovieService {
    /**Inject a reference to javax.validation.Validator if you prefer to work with the JSR-303 API directly.
     * Inject a reference to org.springframework.validation.Validator if your bean requires the Spring Validation API
     */
    @Inject
    private Validator validator;

    public void create(Movie movie){
        validator.validate(movie);
        // ...
    }
}

13.3.Resources

 

  • 다운로드

    다음에서 sample 코드를 포함하고 있는 Eclipse 프로젝트 파일을 다운받은 후, 압축을 해제한다.

    • Maven 기반 실행

      Command 창에서 압축 해제 폴더로 이동한 후, mvn compile exec:java -Dexec.mainClass=...이라는 명령어를 실행시켜 결과를 확인한다. 각 Eclipse 프로젝트 내에 포함된 Main 클래스의 JavaDoc을 참고하도록 한다.

    • Eclipse 기반 실행

      Eclipse에서 압축 해제 프로젝트를 import한 후, src/main/java 폴더 하위의 Main.java를 선택하고 마우스 오른쪽 버튼 클릭하여 컨텍스트 메뉴에서 Run As > Java Application을 클릭한다. 그리고 실행 결과를 확인한다.

    표 13.1. Download List

    Name Download
    anyframe.sample.validation.payload.zip Download
    anyframe.sample.validation.groups.zip Download
    anyframe.sample.validation.custom.zip Download


 

 


출처 - http://dev.anyframejava.org/docs/anyframe/plugin/foundation/4.6.1/reference/html/ch13.html








스프링 MVC에서 지원하는 @Valid를 통한 데이터 검증은 정말 놀랍다. 특히 브라우저에서 클라이언트가 입력자료를 넘겨줄 때 이 자료를 검증할 수 있는 모델을 매우 손쉽게 만들 수 있다는 점이다. @Valid는 스프링이 만든 기술은 아니며 최근 JSR-303이란 이름으로 채택된 서블릿 2.3 표준스펙 중 하나인데 매번 그렇듯 스프링은 이 새로운 표준을 확장하고 쉽게 사용할 수 있도록 스프링만의 방식으로 재편성해주었다. 


@Valid가 간소화 될 수 있었던 배경을 이해하고 응용할 수 있게끔 학습하는게 중요하겠지만 먼저 @Valid가 얼마나 대단한지 보여주기 위해 맛보기로 간단한 @Valid 예제를 살펴보고자 한다. 먼저 자바빈 객체를 하나 만들어보자.

public class User {

@Size(min=5, max=50) private String id;
@Size(min=5, max=50) private String password;
@Pattern(regexp="^[_0-9a-zA-Z-]+@[0-9a-zA-Z]+(.[_0-9a-zA-Z-]+)*$")
private String email;

… get/set 생략 …
}

그리고 이 JSP파일을 처리할 수 있는 컨트롤러를 하나 만들어 보자. 최초에 문서를 불러들이는 Get과 송신을 위해 필요한 Post, 2가지가 필요하다. 그리고 세션으로 상태유지를 할 수 있게끔 해당 요소를 @SessionAttributes로 공유해주는 것까지 해주도록 하자.

이 몇줄의 코드를 설명시키기 위해 토비의 스프링에서 무려 200쪽이 넘는 공간을 할애하여 설명하고 있으므로 여기서는 가급적 자세한 설명을 생략하도록 하겠다. (생략의 가장 중대한 이유는 필자도 잘 모른다는 사실이지만 그것은 중요치 않다.... :D) 먼저 @ModelAttribute User user와 같은 형태로 클라이언트의 요청을 커맨트 패턴으로 받는다. 그리고 이 모델에 검증을 하기 위해 위의 예제와 같이 @valid라는 검증 어노테이션을 붙인다. 그리고.... 더 해야 할 일은...... 없다.

짜잔! 이것은 기본적인 검증 모델 설계가 끝난 셈이다. 믿지 않겠지만 진짜다. 스프링 MVC는 이와같은 간단한 어노테이션만으로도 검증모델을 유지하며 검증 결과를 BindingResult 객체로 전달해주는 역할을 담당한다. 먼저 자바빈 객체를 살펴보며 @Valid가 무엇인지 부터 알아보자.

@Valid는 그동안 논란이 되왔던, 검증모델이 프리젠테이션 계층에 위치해야 되는지, 서비스 계층에 위치해야 하는지에 대한 논란을 어느 정도 통일시켜주는 표준기술이다. 그러나 역설적으로 @Valid는 그동안의 논란을 뒤집고 검증모델을 프리젠테이션 계층도, 서비스 계층도 아닌 도메인 계층에 삽입할 수 있게 하였다. 즉 @Valid는 아예 원천적으로 등록 오류를 피하기 위해 객체 자체에 검증모델 주입하는 방식을 채택하고 있다.

@Valid에는 기본적으로 14개의 검증 어노테이션을 제공한다.

@AssertFalse : false 값만 통과 가능
@AssertTrue : true 값만 통과 가능
@DecimalMax(value=) : 지정된 값 이하의 실수만 통과 가능
@DecimalMin(value=) : 지정된 값 이상의 실수만 통과 가능
@Digits(integer=,fraction=) : 대상 수가 지정된 정수와 소수 자리수보다 적을 경우 통과 가능
@Future : 대상 날짜가 현재보다 미래일 경우만 통과 가능
@Past : 대상 날짜가 현재보다 과거일 경우만 통과 가능
@Max(value) : 지정된 값보다 아래일 경우만 통과 가능
@Min(value) : 지정된 값보다 이상일 경우만 통과 가능
@NotNull : null 값이 아닐 경우만 통과 가능
@Null : null일 겨우만 통과 가능
@Pattern(regexp=, flag=) : 해당 정규식을 만족할 경우만 통과 가능
@Size(min=, max=) : 문자열 또는 배열이 지정된 값 사이일 경우 통과 가능
@Valid : 대상 객체의 확인 조건을 만족할 경우 통과 가능

기본 객체는 대부분의 경우 위의 14가지 어노테이션으로도 통과가 가능하겠지만 생성객체나 좀 더 엄격한 규칙을 위해서는 직접 검증 어노테이션을 구현할 수도 있다. 직접 어노테이션을 구현하는 부분은 다음에 다뤄보기로 하고 지금은 위의 예제가 어떤 방식으로 동작하는 지에 좀 더 집중하도록 하겠다.

이제 위와 같은 어노테이션을 도메인 모델에 적용시켰다면 마지막으로 스프링은 @Valid를 컨트롤러에 적용시킴으로써 깜찍한 마법을 부려준다. 위의 예제에서 @ModelAttributes @Valid User user는 위의 어노테이션 설명에서도 보았듯이 대상 객체에 정의 되있는 확인 조건을 만족시키는 역할을 담당한다. 스프링 MVC는 이런 조건을 만족시키지 못할 경우 내부 컨트롤러에 의해 자동적으로 bindingResult 객체에 담겨저 컨트롤러로 돌아오게 된다.

즉 위의 예제는 /join URL을 Post방식으로 호출할 경우 자연스럽게 @ModelAttribute가 해당 파라미터들을 User객체로 캡슐화 시킴과 동시에 내부에 규약되있는 객체 검을을 @Valid가 발동되면서 오류가 있을 경우 오류 정보를 함께 전송한다. 그리고 마지막으로 스프링 컨테이너에 의해 오류정보는 bindingResult 객체로 분류되면서 끝이 난다.

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<form:form modelAttribute="user" autocomplete="off">
<h4><strong>회원가입</strong></h4>
<div class="clearfix">
<div class="left">
<form:label path="id">아이디</form:label>
</div>
<div class="left">
<form:input path="id"/>
<form:errors path="id" />
</div>
</div>
<div class="clearfix">
<div class="left">
<form:label path="password">비밀번호</form:label>
</div>
<div class="left">
<form:input path="password"/>
<form:errors path="password" />
</div>
</div> 
<div class="clearfix">
<div class="left">
<form:label path="email">이메일</form:label>
</div>
<div class="left">
<form:input path="email"/>
<form:errors path="email" />
</div>
</div>
<div class="clearfix">
<div class="left"></div>
<div class="left"></div>
</div>
<input type="submit" value="업로드" />
</form:form>


아래는 위의 JSP코드와 컨트롤러를 이용하여 만든 예제 회원가입 폼이다.

위의 코드를 적용시키기 위해서는 Spring에서 제공하는 form 태그를 이해해야 하는데 스프링 폼은 나중에 깊게 다루기로 하고 일단 기본적으로 이 스프링 폼을 통해 에러객체와 폼객체를 완벽하게 분리시켰다는 사실만 기억하자.

<form:errors>는 컨트롤러에서 전달받은 에러 객체를 컨트롤하는 역할을 담당하는데 현재 별도의 에러 메시지 설정이 없으므로 기본 에러가 표시된다. 만약 에러메시지를 바꾸고 싶다면 도메인 객체에 삽입한 검증 모델에 message라는 값을 더해주면 된다.

@Size(min=5, max=50, message="5자에서 50자 사이의 값만 가능합니다") private String id;

또 하나의 방법은 messages.properties를 이용하여 이런 에러메시지를 관리하는 방법인데 이 방법은 좀 더 스프링 검증 모델을 완벽히 습득한 뒤에 작성하도록 하겠다. 생각같아선 가급적 @Valid에 대해 완벽하게 다루고 싶었지만 생각보다 복잡한 방식으로 이루어지고 있는데다 확장포인트를 잡기가 어려워 여기서 대략적인 설명만 달아놓을 수 밖에 없었다.

이 문서는 기본적인 설명만을 다루고 있으므로 예제나 방식을 실제 서비스에 적용할 때는 좀 더 많은 관련 문서를 참고해 보고 자신만의 설계 모델을 구축해야 할 것이다.

 


출처 - http://springmvc.egloos.com/509029








Spring3.0 에서 Validation 기능을 보면 이전 버전
에서 사용했던 Validation 방법 뿐만 아니라
새로운 기능들이 추가가 되었습니다.
그중에서 단연 꽃은 JSR-303 스펙 입니다.
혹자는 이것을 Spring 3.0 Validation이라고 합니다.
JSR-303스펙은 쉽게 말해서 자바의 모든 클래스 자체에
검증 로직을 annotation으로 설정 해서 검증이
필요한 어느 곳이든 해당 검증을 수행 할 수 있도록
하는 J2EE Spec 입니다.
ㅋㅋ 저 답지 않게 유식한 척을 했군요
아래 코드를 보면서 간략 하게 설명 드리겠습니다.

public class PersonForm {

@NotNull
@Size(max=64)
private String name;

@Min(0)
private int age;

}


위에 코드를 보면 name 속성은 반드시 값이 존재 해야 하고,
length는 64자를 넘어서는 안된 다는 것입니다.
이렇게 클래스에 검증 로직을 넣고 검증이 필요한 곳
에서 실행을 하면 됩니다.


    * Library 설정

먼저 Spring 3.0 에서 JSR-303 스펙을 구현 하기 위해서는
아래의 라이브러리가 필요 합니다.
(1) jax-validation.jar (JSR-303 스펙 라이브러리)
(2) hibernate-validation.jar (JSR-303 구현체 라이브러리)
위 의 두개의 라이브러리를 다운 받아서 이클립스 classpath
를 설정 합니다.
만약 Maven을 사용 하시는 분들은 아래의 pom.xml을
추가 합니다.

<repositories>
<repository>
<id>jboss</id>
<url>http://repository.jboss.com/maven2</url>
<releases>
   <enabled>true</enabled>
</releases>
<snapshots>
   <enabled>false</enabled>
</snapshots>
</repository>
</repositories>

<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.0.0.Beta2</version>
</dependency>
</dependencies>


스프링 3.0 full pom.xml 설정을 원하 시는 분들은
제 블러그 “Spring3.0 관련 라이브러리 (pom.xml)”
을 참조 하시면 됩니다.

    * 검증 클래스 작성


package org.springshowcase.mvc;

import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import org.hibernate.validation.constraints.NotEmpty;


public class User {

@NotEmpty()
@Size(min=1,max = 5)
@Pattern(regexp=”[0-9a-zA-Z]”)
private String id;

@NotEmpty()
private String name;

@NotEmail
private String email;

private Integer age;

 …setter & getter method
}


위의 소스를 보면 전형적인 일반 자바 빈즈
클래스 형태 입니다.
“id” 속성의 검증 로직은 아래와 같습니다.
- 반드시 값이 존재 해야함
- 반드시 길이는 1이상 5이하여야 함 (byte 기준 아님)
- 반드시 영문 또는 숫자 형태여야함
“email” 속성에서 “@NotEmail”은 제가 직접
만든 annotation입니다. 이부분은 뒤에서
설명 하도록 하겠습니다.

    * Spring 빈 설정

스프링에서 JSR-303 스펙을 적용 하기 위해서는
다소 복잡한 과정을 거쳐야 합니다.
“LocalValidatorFactoryBean” 빈을 설정 하고 또 이 빈을
“AnnotationMethodHandlerAdapter” 선언된 빈에
injection 해야 하고…
이런거 필요 없이 spring3.0 에서 아주 간단한
네임스페이스를 제공 합니다.

<mvc:annotation-driven />


위와 같이 선언 하면 스프링에서 JSR-303 적용은
끝입니다.

Comming Up Next…

- Controller에서 Validation 사용 하는 방법
- JSR-303 기반 Message 번들 적용 방법
- JSP 설정 방법
- 커스컴 Validation Annotation 작성 방법
- Spring Validation VS JSR 303


 

 

 

Spring 3.0 Validation Part1“에 이어서 설명 드리도록 하겠습니다.
먼저 이전에 작성한 User 도메인 클래스 입니다.

package org.springshowcase.mvc;

import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import org.hibernate.validation.constraints.NotEmpty;


public class User {

@NotEmpty()
@Size(min=1,max = 5)
@Pattern(regexp="[0-9a-zA-Z]")
private String id;

@NotEmpty()
private String name;

@NotEmail
private String email;

private Integer age;

...setter & getter method
}


* Controller에서 Validation 사용 하는 방법

Controller 에서 사용 하는 방법은 의외로 간단 합니다.

@RequestMapping(method = RequestMethod.POST)
public ModelAndView processSubmit(@Valid @ModelAttribute("user") User user,
BindingResult result, SessionStatus status) {
ModelAndView mav = new ModelAndView();
if (result.hasErrors()) {
System.out.println(".....invalid");
}
return mav;
}


위의 소스를 보면 검증을 위해서 파라미터 부분에
“@Valid” 어노테이션을 설정하면 끝입니다.
즉 저말은 User 도메인에 검증 로직을 실행 하라
는 뜻입니다. 이미 검증이 끝났기 때문에
“result.hasErrors”를 통해서 validation
분기 로직을 적절하게 작성 하면 됩니다.

* JSP 설정 방법

<form:input path="id" /> <br />
<font color="yellow"><form:errors path="id" /></font><br />
<label for="contact_email">Enter user name: </label> <br />
<form:input path="name" /><br />
<font color="yellow"><form:errors path="name" /></font><br />
<label for="contact_email">Enter email: </label> <br />
<form:input path="email" /><br />
<font color="yellow"><form:errors path="email" /></font><br />
<label for="contact_subject">Enter Age: </label> <br />
<form:input path="age" /> <br /><br />
<input type="button" value="Save Changes" onclick="doSave();" class="button" />
</form:form>


JSP 소스는 이전 버전과 동일 하게 위와 같이 코드를
작성 합니다.

* Invalid시 Message 리소스 적용 하기

이 부분은 SpringSource 팀 블러그와 JSR-303 스펙을
보면서 알아낸 방법 입니다.매우 중요한 부분이니
잘 이해하시기 바랍니다.

먼저 아래와 같이 메세지 번들을 설정 합니다.

<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>messages.default</value>
</list>
</property>
</bean>

default_ko.properties
메세지 expression은 “검증 annotation명+도메인명+속성명”
으로 하며 key 작성 규칙은 java method 형식으로
합니다.
NotEmpty.user.id=아이디를 입력 하세요.
Size.user.id=아이디는 5자 이하 입니다.
Pattern.user.id=아이디는 반드시 영문 또는 숫자만 입력 가능 합니다.
NotEmpty.user.name=이름을 입력 하세요.
NotEmail.user.email=이메일 형식이 틀립니다.

public class User {

@NotEmpty()
@Size(min=1,max = 5)
@Pattern(regexp="[0-9a-zA-Z]")
private String id;

.....
}

예를 들어서 “@NotEmpty”의 경우 메세지 키는
NotEmpty(annotation 명) + “.” + user(도메인 클래스명)
+”.” + id(속성명) 그래서 전체 메세지 키는
“NotEmpty.user.id”가 되는 것입니다.

Part3에서는 Custom Validation 작성 방법에
대해서 말씀을 드리겠습니다.

 


 

Part1,Part2는 기본적인 스프링에서 JSR-303
Validation 사용 방법에 대해서 설명 드렸습니다.
이번 Part는 Custom Validation에 대해서
말씀 드리겠습니다.
먼저 User 도메인 클래스 보면 아래와
같습니다.

public class User {

@NotEmail
private String email;

.....
}

,
“@NotEmail”은 제가 직접 만든 검증
annotation 입니다.
이제 부터 작성 방법에 대해서 말씀 드리겠습니다.

* NotEmail Annotation 작성 방법

custom validation을 만들려면 2개의 클래스가 필요 합니다.
첫번째는 검증 annotation 클래스 이며 나머지 하나는
annotation을 검증할 validator 클래스 입니다.
이 두클래스의 참조 관계는 서로 의존 하는 관계
입니다.

package org.springshowcase.mvc;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.ConstraintPayload;

@Documented
//@Constraint(validatedBy = EmailValidator.class)
@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface NotEmail {
public abstract String message() default "Not Email Format!";
public abstract Class<?>[] groups() default {};
public abstract Class<? extends ConstraintPayload>[] payload() default {};
}

“@Contraint” 부분을 주석 처리 하고 compile을 합니다.
이유는 아직 “EmailValidator” 클래스가 존재 하지 않기
때문입니다.

* EmailValidator 클래스 작성 방법

package org.springshowcase.mvc;


import java.util.regex.Pattern;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

public class EmailValidator implements ConstraintValidator<NotEmail, String> {

@Autowired
@Qualifier("emailPattern")
String pattern;

public void initialize(NotEmail constraintAnnotation) {
// nothing to initialize
}

public boolean isValid(String value, ConstraintValidatorContext context) {
Pattern EMAIL_PATTERN = Pattern.compile(this.pattern);
return EMAIL_PATTERN.matcher(value).matches();
}
}


반드시 “ContraintValidator” 인터페이스를 구현 해야 합니다
“@Autowired” 어노테이션을 보면 스프링 annotation 입니다.
이 얘기는 아래와 같이 스프링 빈을 선언 하면

<mvc:annotation-driven />


“ContraintValidator” 인터페이스를 구현한 모든 클래스는
스프링 ApplicationContext에서 이러한 클래스들을
스프링 빈으로 생성합니다.
이말은 Validator에 다른 스프링 빈을 injection을 할 수있다는
뜻 입니다.
예제를 통해서 email 정규식을 일부러 스프링 빈으로 설정하고
injection을 시켰습니다.
아래는 injection 당한 빈 입니다.

<bean id="emailPattern" class="java.lang.String">
<constructor-arg index="0" type="java.lang.String"
value=".+@.+\\.[a-z]+" />
</bean>


* NotEmail Annotation 재컴파일

이제 “EmailValidator”가 생성이 되었기 때문에
“NotEmail” 어노테이션 클래스의 주석을 해제하고
재컴파일 합니다.

package org.springshowcase.mvc;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.ConstraintPayload;

@Documented
@Constraint(validatedBy = EmailValidator.class)
@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface NotEmail {
public abstract String message() default "Not Email Format!";
public abstract Class<?>[] groups() default {};
public abstract Class<? extends ConstraintPayload>[] payload() default {};
}


* Message Bundle 작성 방법

이제 새로운 custom validator를 작성이 끝났습니다.
새로운 annotation을 적용할 도메인 클래스에
선언 하면 됩니다.
메세지 번들 작성 방법은 아래와 같이 하면 됩니다.
NotEmail.user.email=이메일 형식이 틀립니다.

* Spring Validation VS JSR-303

JSR-303 스펙에 대해서 확정을 하기 위한 투표를
하려고 했을때 스프링 진영에서는 참석을 하지
않았다고 합니다. 그래서 jboss 진영에서 심기가
좋지 않았다는 얘기가 있습니다.
역시 둘은 앙숙인듯….
아무래도 스프링 입장에서 자기들 validation이
있는데 굳이 JSR-303 스펙을 넣는게 그다지
좋지는 않았을 것 같습니다.
anyway!
JSR-303은 제가 도메인 클래스를 얘를 들었지만
사실 도메인 클래스만 해당 되는 것은 아닙니다.
모든 클래스(Service,DAO,..)에서 적용이 가능
합니다.
직접 써보니 JSR-303은 장점이 상당히 많았습니다.
첫째 검증 로직의 중복이 없습니다.
대부분 스프링 validation은 매요청시 작성 한거에
반해서 JSR-303은 도메인에 설정 하기때문에
검증 로직이 좀더 명확하고 코딩수도 적다는 얘기
입니다. 그리고 메세지 작성 방법도 직관적이기
때문에 관리도 수월 합니다.
반면 단점이 한나 있습니다. 좀 유연하지 않는다는
문제 입니다.
무슨 얘기냐 예를 들어서 id,name,email 이런 3개의
속성이 있다고 하고 모두 NotNull 조건이라고
설정을 합니다.
insert일 경우야 당연히 NotNull이지만
name만 update를 할경우 id,name 두
속성만 있으면 되는데 validation 조건이
모두 NotNull 조건이기 때문에 굳이
필요 없는 email에 기존 값을 채워서
보내야 한다는 것입니다.
즉 케이스별 검증 체크를 선택적으로 할 수
없다는 것입니다.
이런 이유는 예측컨데 jboss 진영에 힘이
있었지 않나 생각 합니다.
hibernate인 경우는 전체 업데이트를
체크 하기 때문이죠..
그래서 제가 개인적으로 시간이 되면 선택적으로
validation을 수행하는 util 클래스를 만드려고 합니다.
(언제가 될지 모르지만..)
그리고 한가지 더 현재 JSR-303 라이브러리와 구현체
라이브러리가 release가 아닌 running 중이기 때문에
라이브러리의 버전 진행도 유심히 모니터링
할 필요가 있습니다.
개인적으로 JSR-303은 실무에서 사용해도
훌륭한 개발 도구가 될 것 같습니다.

* 소스 다운 로드

본 예제는 제가 진행하는 오픈 소스 “ssc-mvc”를 체크아웃
받으시면 됩니다.
자세한 내용은 오픈 소스 사이트를 참고 하시기 바랍니다.

 


출처 - http://beyondj2ee.tumblr.com/post/14509177207/spring-3-0-validation-part3








 다양한 기초 예제들이 있다.


http://www.roseindia.net/tutorial/spring/spring3/web/spring-3-mvc-validation-example.html


This tutorial shows you how to validate Spring 3 MVC based applications. In Spring 3 MVC annotation based controller has been added and other type of controllers are not deprecated.

In this example application we will be create a Validation form in  Spring 3.0  MVC  Framework. The validation of the form is done using annotation. The Hibernate validator framework is used for validation of the form data. 

The application will present simple user registration form to the user. Form will have the following fields:

  1. User Name
  2. Age
  3. Password

The validator framework will validate the user input. If there is any validation error application will display the appropriate message on the form.

To use the validation framework in the application "hibernate-validator-4.0.2.GA.jar" and " validation-api-1.0.0.GA.jar" must be added to the project libraries.

Application uses following jar files:




Step 1:
Create index.jsp  under WebContent . The code index.jsp as :

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"

pageEncoding="ISO-8859-1"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">

<title>Spring 3, MVC Examples</title>

</head>

<body>

<h1>Spring 3, MVC Examples</h1>

<ul>

<li><a href="forms/validationform.html">Validation Form</a></li>

</ul>

</body>

</html>

In the above page we are creating a new link to access the Validation Form example from the index page of the application.

Step 2:

Now  we will create a  model class " ValidationForm.java"  under src folder . The code of   "ValidationForm.java" is:

package net.roseindia.form;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.Size;
import javax.validation.constraints.NotNull;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.format.annotation.NumberFormat;
import org.springframework.format.annotation.NumberFormat.Style;

public class ValidationForm {
       
@NotEmpty
       
@Size(min = 1, max = 20)
       
private String userName;
       
@NotNull
       
@NumberFormat(style = Style.NUMBER)
       
@Min(1)
       
@Max(110)
       
private Integer age;
       
@NotEmpty(message = "Password must not be blank.")
       
@Size(min = 1, max = 10, message = "Password must between 1 to 10 Characters.")
       
private String password;

       
public void setUserName(String userName) {
               
this.userName = userName;
       
}

       
public String getUserName() {
               
return userName;
       
}

       
public void setAge(Integer age) {
               
this.age = age;
       
}

       
public Integer getAge() {
               
return age;
       
}

       
public void setPassword(String password) {
               
this.password = password;
       
}

       
public String getPassword() {
               
return password;
       
}
}

In the above class we have added the proper annotation for validating the form values. Here is the list of annotation used for validation:

@NotEmpty
@Size(min = 1, max = 20)
@NotNull
@NumberFormat(style = Style.NUMBER)
@Min(1)
@Max(110)
@NotEmpty(message = "Password must not be blank.")
@Size(min = 1, max = 10, message = "Password must between 1 to 10 Characters.")

Step 3 :

Now create a folder views under WebContent/WEB-INF . Again  cerate "validationform.jsp"  under WebContent/WEB-INF/views as :

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"

pageEncoding="ISO-8859-1"%>

<%@taglib prefix="form" uri="http://www.springframework.org/tags/form"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">

<title>Insert title here</title>

</head>

<body>

<form:form method="post" action="validationform.html"

commandName="validationForm">

<table>

<tr>

<td>User Name:<font color="red"><form:errors

path="userName" /></font></td>

</tr>

<tr>

<td><form:input path="userName" /></td>

</tr>

<tr>

<td>Age:<font color="red"><form:errors path="age" /></font></td>

</tr>

<tr>

<td><form:input path="age" /></td>

</tr>

<tr>

<td>Password:<font color="red"><form:errors

path="password" /></font></td>

</tr>

<tr>

<td><form:password path="password" /></td>

</tr>

<tr>

<td><input type="submit" value="Submit" /></td>

</tr>

</table>

</form:form>

</body>

</html>

In this above jsp file <form:errors path="..." />  tag is used to display the validation error messages.

Step 4:

Now  create "validationsuccess.jsp" file under  WebContent/WEB-INF/views . The code of  "validationsuccess.jsp"  is :

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"

pageEncoding="ISO-8859-1"%>

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

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">

<title>Insert title here</title>

</head>

<body>

<core:out value="${validationForm.userName}" /> Save Successfully.

</body>

</html>

Step 5:

Now create  Contoller class "ValidationController.java" under src folder.  Controller class use for  RequestMapping  and process the user request. The  code of   "ValidationController.java"   as:

package net.roseindia.controllers;

import net.roseindia.form.ValidationForm;
import java.util.Map;
import javax.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/validationform.html")
public class ValidationController {

       
// Display the form on the get request
       
@RequestMapping(method = RequestMethod.GET)
       
public String showValidatinForm(Map model) {
               
ValidationForm validationForm = new ValidationForm();
                model
.put("validationForm", validationForm);
               
return "validationform";
       
}

       
// Process the form.
       
@RequestMapping(method = RequestMethod.POST)
       
public String processValidatinForm(@Valid ValidationForm validationForm,
                       
BindingResult result, Map model) {
               
if (result.hasErrors()) {
                       
return "validationform";
               
}
               
// Add the saved validationForm to the model
                model
.put("validationForm", validationForm);
               
return "validationsuccess";
       
}

}

The @RequestMapping annotation is used for request uri mapping.

Step 6:

Now modify web.xml as:

<?xml version="1.0" encoding="UTF-8"?>

<web-app version="2.5"

xmlns="http://java.sun.com/xml/ns/j2ee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_5.xsd">

<display-name>Spring3Example</display-name>

<servlet>

<servlet-name>dispatcher</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>dispatcher</servlet-name>

<url-pattern>/forms/*</url-pattern>

</servlet-mapping>

<welcome-file-list>

<welcome-file>index.jsp</welcome-file>

</welcome-file-list>

</web-app>

Step 7:

Again create "dispatcher-servlet.xml"  under  WebContent/WEB-INF . The "dispatcher-servlet.xml"  code as :

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:p="http://www.springframework.org/schema/p"

xmlns:context="http://www.springframework.org/schema/context"

xmlns:mvc="http://www.springframework.org/schema/mvc"

xsi:schemaLocation="

http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-3.0.xsd

http://www.springframework.org/schema/mvc

http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd

">

<!-- Enable annotation driven controllers, validation etc... -->

<mvc:annotation-driven />

<context:component-scan base-package="net.roseindia.controllers"/>

<bean id="viewResolver"

class="org.springframework.web.servlet.view.InternalResourceViewResolver">

<property name="prefix">

<value>/WEB-INF/views/</value>

</property>

<property name="suffix">

<value>.jsp</value>

</property>

</bean>

<bean id="messageSource"class="org.springframework.context.support.ReloadableResourceBundleMessageSource">

<property name="basename" value="/WEB-INF/messages" />

</bean>

</beans>

Where <mvc:annotation-driven> is used for annotation driven controllers and validation. While message Resource configuration is done in "dispatcher-servlet.xml" using following entry:

<bean id="messageSource"class="org.springframework.context.support.ReloadableResourceBundleMessageSource">

<property name="basename" value="/WEB-INF/messages" />

</bean>

Again  create customization  Message file  "messages.properties"  under  WebContent/WEB-INF. The modify  "messages.properties"  as

NotEmpty.validationForm.userName=User Name must not be blank.

Size.validationForm.userName=User Name must between 1 to 20 characters.

Step 8:

Now run Application display output as :

if click  Validation Form  hyperlink then open validationform as :

Step 9:

Now Enter  userName,  Age and Password if find any error display as:

  

Again display  error-

If validation  success then display validationsucess page as :

Download example code

In this we have studies the use of annotation controller and annotated validators.

 



출처 - http://sway.tistory.com/entry/Spring-3-MVC-Validation-Example








org.springframework.validation.BindException 에 대한 한가지 해결 방안

아래 post에 의하면 @Valid 적용 객체 다음에 BindingResult가 바로 와야한다.



I got a new roo project off the ground successfully, but now I'm having a problem getting validation to work for a login page. It seems that the validator is choking before passing control to my controller. I am never given the opportunity to check the BindingResult. I've examined several similar questions here and on the web, and my code seems to conform with what they are doing.

First the error I get when submitting the form (if i pass validation i get no error message). In this case i didn't meet the minimum length for the password:

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'loginUser' on field 'password': rejected value [b]; codes [Size.loginUser.password,Size.password,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [loginUser.password,password]; arguments []; default message [password],50,5]; default message [Password must be between 1  and 50 characters long]
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:894)
    org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:789)

My entity validation is setup like this:

public class LoginUser {

    @NotNull
    @NotEmpty
    private String username;

    @NotNull
    @NotEmpty(message = "Password must not be blank.")
    @Size(min = 5, max = 50, message = "Password must be between 1 " 
            + " and 50 characters long")
    private String password;
}

Here is my markup:

<form:form method="post" commandName="command">
    <form:label path="username">Username: </form:label>
    <form:input path="username"/>
    <form:errors path="username"/>

    <form:label path="password">Password: </form:label>
    <form:password path="password"/>
    <form:errors path="password"/>

    <input type="submit"/>
</form:form>

And the controller:

    @ModelAttribute("command")
    public LoginUser fbo(){
        LoginUser u = new LoginUser();
        u.setUserType(UserType.USER);
        return u;
    }

    @RequestMapping(value="/login.htm", method=RequestMethod.POST)
    public String doLogin(@Valid LoginUser command, 
            HttpServletRequest request, BindingResult result
        ){
        if(result.hasErrors()){
            return "login";
        }
    }

And just in case it matters, the generated xml in webmvc-config.xml:

<!-- The controllers are autodetected POJOs labeled with the @Controller annotation. -->
<context:component-scan base-package="com.tcg.myproject" use-default-filters="false">
    <context:include-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
</context:component-scan>

<!-- Turns on support for mapping requests to Spring MVC @Controller methods
     Also registers default Formatters and Validators for use across all @Controllers -->
<mvc:annotation-driven conversion-service="applicationConversionService"/>

Been pulling my hair out for hours and I can't figure out what it could be. Thanks for reading!

share|improve this question
Why didn't you simply use Spring Security add-on? – bhagyas Mar 29 '12 at 3:48
One does not simply use spring security. – Matthew Gilliard Feb 13 at 10:43

In your controller handler method, try moving the BindingResult argument so it is immediately after the command argument. Spring looks for command object parameters and BindingResult parameters to be paired up in handler method signatures.

share|improve this answer
Thanks Scott! Not sure I would have ever found this w/o asking. – samspot Mar 29 '12 at 15:38
No problem. Also note that if you have multiple command objects in the method signature, each one should be followed by its own BindingResult - i.e. they must travel in pairs. – Scott Frederick Mar 30 '12 at 17:31
Awesome, thanks again. – samspot Mar 30 '12 at 18:47
Good to know this about the pairing, thanks Scott – frandevel Dec 22 '12 at 12:18











 


출처 - http://stackoverflow.com/questions/9916623/spring-valid-validator-not-invoked-properly-roo-hibernate






Email Regular Expression Pattern

^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*
      @[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$;


Date Format

    @DateTimeFormat(pattern="MM/dd/yyyy")
    @NotNull @Past
    private Date birthday;

 

728x90
반응형
블로그 이미지

nineDeveloper

안녕하세요 현직 개발자 입니다 ~ 빠르게 변화하는 세상에 뒤쳐지지 않도록 우리모두 열심히 공부합시다 ~! 개발공부는 넘나 재미있는 것~!

,