ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [11] 추상화 - Validation 추상화
    Spring/Spring 핵심 기술 2020. 4. 22. 09:55
    반응형

    org.springframework.validation.Validator는 application에 사용하는 data 검증용 interface입니다. 

     

    Validator는 주로 springMVC에서 사용하지만 web 계층 전용 Validator는 아닙니다. web, service, data 모든 계증에서 사용할 수 있는 일반적인 interface 입니다.

     

    객체 data의 validation check를 위해 bean validation이 지원하는 validation용 annotation을 사용하거나 직접 validator를 만들어 사용할 수 있습니다.

    더보기

    *Bean Validation의 정의

    bean validation은 JavaBean을 검증하기 위한 metadata model과 API입니다. metadata source는 annotation입니다. 이 annotation은 XML validation descriptor를 사용하여 meta-data를 확장하거나 재정의 할 수 있습니다.

     

     

    validator를 테스트하기 위해 field와 getter, setter가 있는 간단한 class(Event.java)를 하나 만듭니다. 

    public class Event {
    
        Integer id;
    
        String title;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getTitle() {
            return title;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    }
    

     

     


     

    [Validator 만들기]

     Validator는 Validator interface를 구현하여 만듭니다. 이 때 supports()와 validate() method를 모두 구현해줘야 합니다.

        1.supports() -> 매개값으로 넘어온 class가 검증 가능한 class인지 확인하는 method입니다.

        2.validate() -> 실제로 data의 유효성을 검증하는 method입니다. 구현 시 ValidationUtils를 사용할 수 있습니다.

    validator를 만드는 예는 다음과 같습니다.

    public class EventValidator implements Validator {
    
        @Override
        public boolean supports(Class<?> clazz) {    //①
            return clazz.getClass().isInstance(Event.class);
        }
    
        @Override
        public void validate(Object target, Errors errors) {  //②
            ValidationUtils.rejectIfEmptyOrWhitespace(
                    errors,     //Errors object
                    "title",    //title
                    "notempty", //errorCode : error message의 key값입니다.
                    "Empty title is not allowed."   //defaultMessage : errorCode로 message를 찾지 못했을 때 보여줄 message입니다.
            );
    
            //③
            Event event = (Event)target;
            if(event.getTitle() == null) {
                //객체 전반적인 error 확인 시
                errors.reject(
                    "notempty", //errorCode
                    "Empty title is not allowed." //defaultMessage
                );
    
                //객체의 특정 field에 대한 error 확인 시
                errors.rejectValue(
                    "title", 	//field
                    "notempty", //errorCode
                    "Empty title is not allowed." //defaultMessage
                );
            }
        }
    }

    ①validator의 supports() method입니다. 이 method에서는 검증하려는 객체가 validator에 상응하는 type인지 확인하여 동일한 type이면 true 아니면 false를 return하도록 합니다.

     

    ②validator의 validate() method 입니다. ValidationUtils에 미리 정의된 validation용 method를 사용할 수 있습니다. 여기서는 rejectIfEmptyOrWhitespace() method를 사용하여 null이거나 whitespace인 경우 error로 취급하도록 작성하였습니다. 

     

    ValidationUtils을 사용하지 않고도 validate() method를 작성할 수 있습니다.(②, ③은 원하는 방법으로 취사선택 가능합니다.) 우선 검증할 객체를 실제 객체의 type으로 형변환 해주고 해당 객체의 data가 error 조건에 해당될 때(여기서는 title filed가 null인 경우) error객체의 reject() 또는 rejectValue() method를 사용하여 error code에 해당하는 error를 발생시킵니다. reject()는 객체 전체의 data에 대한 유효성을 검사할 때 사용하고 rejectValue()는 객체의 특정 field에 대한 유효성을 검사할 때 사용합니다.

     

     

    [Validator의 사용]

    AppRunner.java에서 Validator를 사용해보도록 하겠습니다. 

    @Component
    public class AppRunner implements ApplicationRunner {
    
        @Override
        public void run(ApplicationArguments args) throws Exception {
    
            Event event = new Event();
            EventValidator eventValidator = new EventValidator();
            
            Errors errors = new BeanPropertyBindingResult(	//①
                    event,  //target : 유효성을 검증할 객체를 지정함니다.
                    "event" //객체의 이름을 지정합니다.
            );
    
            
            eventValidator.validate(        //②
                    event,  //검증할 객체입니다.
                    errors  //검증할 에러를 담습니다.
            );
    
            System.out.println(errors.hasErrors()); //errors에 해당하는 Error가 있는지 확인합니다.
    
            errors.getAllErrors().forEach(e -> {        //③
               System.out.println("====== error code ======");
               Arrays.stream(e.getCodes()).forEach(System.out::println);    //Error code들을 출력합니다.
               System.out.println(e.getDefaultMessage());   //defaultMessage를 출력합니다.
            });
        }
    }

    ①JavaBean 객체에 error를 binding하고 error를 평가하기 위해 Errors와 BindingResult interface를 구현한 type이 BeanPropertyBindingResult class입니다. 해당 class의 객체를 생성합니다.

    ②객체의 data가 올바른지 검증하기 위해 검증할 객체와 errors객체를 매개값으로 하여 validator객체의 validation() method를 호출합니다. source상 Event class의 title field가 empty이기 때문에 Errors 객체에 error를 담아줍니다. error codesms 사용자가 만든 error code 외에 다음과 같은 3가지 error code를 추가로 만들어 줍니다.

    1. [errorCode].[ObejctName].[field]        (ex) notempty.event.title
    2. [errorCode].[field]                           (ex) notempty.title
    3. [errorCode].java.lang.String               (ex) notempty.java.lang.String

    위와 같이 자동으로 추가로 생성되는 errorCode를 감안하여 해당되는 형태의 errorCode는 명시적으로 만들지 않습니다.

     

    ③모든 error에 대해 각각의 errorCode들과 defaultMessage를 console에 출력합니다.

     

     

    더보기

    Errors interface는 자주 보게 되지만 구현체인 BeanPropertyBindingResult는 springMVC가 자동으로 생성하여 전달하게 되므로 직접 다룰 일은 거의 없습니다.

     

     


     

    [어노테이션 기반의 Validator 사용]

    Spring에서 기본으로 제공하고 있는 Validator bean을 주입받아 어노테이션 기반으로 validation을 checking할 수 있습니다. 

     

    어노테이션 기반의 Validator를 몇가지 test해보기 위해 Event class에 field를 조금더 추가해 보겠습니다. 다음과 같이 코드를 수정합니다.

    public class Event {
    
        Integer id;
    
        @NotEmpty						        //..............①
        String title;
    
        @Min(0)							//..............②
        Integer limit;
    
        @Email							//..............③
        String email;
    
        public Integer getId() { return id; }
    
        public void setId(Integer id) { this.id = id; }
    
        public String getTitle() {
            return title;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public Integer getLimit() { return limit; }
    
        public void setLimit(Integer limit) { this.limit = limit; }
    
        public String getEmail() { return email; }
    
        public void setEmail(String email) { this.email = email; }
    }

    ①annotion이 지정된 field가 null 또는 whitespace인지 validation check 하도록 하는 annotation입니다.

    annotion이 지정된 field의 값이 annotation에 지정된 value 미만인지 validation check 하도록 하는 annotation입니다. 자매품 @Max도 있습니다.

    annotion이 지정된 field의 data가 email형식인지 validation check 하도록 하는 annotation입니다.

     

     

    Validator bean을 주입받아 위의 Event class type의 객체 data를 validation check하도록 해봅시다. 이를 위해 AppRunner.java를 다음과 같이 수정합니다.

    @Component
    public class AppRunner implements ApplicationRunner {
    
        @Autowired
        Validator validator;
    
        @Override
        public void run(ApplicationArguments args) throws Exception {
    
            Event event = new Event();
    
            event.setTitle("");     //title filed에 유효하지 않은 value를 적용
            event.setLimit(-1);     //limit filed에 유효하지 않은 value를 적용
            event.setEmail("@test@test.com"); //email filed에 유효하지 않은 value를 적용
    
    
            Errors errors = new BeanPropertyBindingResult(
                    event,  //target : 유효성을 확인한 객체를 지정함니다.
                    "event" //객체의 이름을 지정합니다.
            );
    
            validator.validate(
                    event,  //target - validation을 확인할 object
                    errors  //errors - validation 확인 작업에 대한 상태 object
            );
    
            System.out.println(errors.hasErrors()); //errors에 해당하는 Error가 있는지 확인합니다.
    
            errors.getAllErrors().forEach(e -> {
               System.out.println("====== error code ======");
               Arrays.stream(e.getCodes()).forEach(System.out::println);    //Error code들을 출력합니다.
               System.out.println(e.getDefaultMessage());   //defaultMessage를 출력합니다.
            });
        }
    }

    Event class의 title, limit, email field에 대해 각각 유효하지 않은 값을 대입하고 실행하면 다음과 같이 각 error가 발생한 각 field들에 대한  errorCode와 defaultMessage를 확인할 수 있습니다.

     

    Annotation 기반 Validator를 사용한 errorCode 출력

    댓글

Designed by Tistory.