Validation 추상화
- Spring에서 org.springframework.validation.Validator는 애플리케이션에서 사용하는 객체 검증용 인터페이스이다.
- 주로 Spring MVC에서 사용하지만 Web 계층에서만 사용하는 전용 개념은 아니다.
특징
- 어떠한 Layer이여도 상관이 없고 모든 Layer(Web, Service , Data) 에서 사용해도 상관없다.
- 구현체 중 하나이면서 JSR-303(Bean Validation 1.0)과 JSR-349(Bean Validation 1.1), JSR-380(BeanValidation 2.0.1 )을 지원한다. (LocalValidatorFactoryBean)
- DataBinder에 들어가서 Binding할 때 같이 사용되기도 한다.
주로 사용하는 @Annotation
- 참고 https://docs.jboss.org/hibernate/beanvalidation/spec/2.0/api/
- NotNull, NotBlank, Email 등등
인터페이스
boolean supports(Class c)
- 어떤 타입의 객체를 검증할 때 사용할 것인지 결정한다
void validate(Object obj, Errors e)
- 실제 검증 로직을 이 안에서 구현한다
- 구현할 때
ValidationUtils
사용하며 구현하는 것이 편리하다
- 구현할 때
- 실제 검증 로직을 이 안에서 구현한다
@Override
public boolean supports(Class<?> c) {
return false; // 인스턴스가 검증 대상 타입인지 체크
}
@Override
public void validate(Object target, Errors errors) {
// 검증 작업
}
구현 예제
- Event 클래스 생성
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;
}
}
- Event 인스턴스에서 title 필드가 NotNull일때 가정
- Event에 대한 Validation을 처리하는 EventValidator 클래스를 Validator 구현하여 생성한다
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
public class EventValidator implements Validator {
@Override
public boolean supports(Class<?> c) {
return Event.class.equals(c);
}
@Override
public void validate(Object target, Errors errors) { //1
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "title","notEmpty","title이 비어있습니다."); //2
}
}
validate
메소드를 생성ValidationUtils
의rejectIfEmptyOrWhitespace()
메소드를 사용하고 매개 변수에 Errors 객체, 필드명 , 에러코드, 메세지 기본값 을 넣는다- 아래에 3번째 매개 변수인
errorCode
는 key값에 해당하는 인터페이스를 가져오는 역할을 한다. 실제 메세지를 가져올 수 있는 코드이다. - 마지막 매개변수인
defalutMessage
는 에러코드를 찾이 못했을 때의 메세지를 넣어준다
AppRunner 구현
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
import java.util.Arrays;
@Component
public class AppRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
// Event 객체 생성하고 에러를 고의적으로 내기 위해 값을 넣지 않는다
Event event = new Event();
// EventValidator 객체를 생성
EventValidator eventValidator = new EventValidator();
// 검증할 Event 객체를 전달하여 Errors 인스턴스를 생성한다
// BeanPropertyBindingResult 기본 구현체로 사용한다
// Spring MVC에서 자동으로 생성해주고 실질적으로 직접 사용하지는 않는다.
Errors errors = new BeanPropertyBindingResult(event, "event");
// event 객체를 검사한다
eventValidator.validate(event, errors);
// errors에 error 가 있는지 확인한다
System.out.println(errors.hasErrors());
// forEach로 에러코드와 기본 메세지 출력
errors.getAllErrors().forEach( e-> {
System.out.println("----error code-----");
Arrays.stream(e.getCodes()).forEach(System.out::println);
System.out.println("e.getDefaultMessage() = " + e.getDefaultMessage());
});
}
}
- 검증할 Event 객체 생성하고 에러를 고의적으로 내기 위해 값을 넣지 않는다
- EventValidator 객체를 생성 후 검증할 Event 객체를 전달하여 Errors 인스턴스를 생성한다.
- Errors 인스턴스를 생성할 때 구현체인
BeanPropertyBindingResult
는 Spring MVC에서 자동으로 생성해주고 실질적으로 직접 사용하지는 않는다. - event 객체를 검사후 errors 객체에 error가 있는지 확인한다.
- forEach문으로 에러코드와 기본 메세지를 출력한다.
- 결과
errors.hasErrors() = true
----error code-----
notEmpty.event.title
notEmpty.title
notEmpty.java.lang.String
notEmpty
e.getDefaultMessage() = title이 비어있습니다.
- 에러코드를 자동으로 생성해주므로 Validator 구현 시 에러코드는 prefix만 설정해주면 된다.
- 이 방법은 가장 원시적인 예제입니다.
- ValidationUtils 만 사용하는 것은 아니다.
- 아래 예제를 확인해보자
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
public class EventValidator implements Validator {
@Override
public boolean supports(Class<?> c) {
return Event.class.equals(c);
}
@Override
public void validate(Object target, Errors errors) {
// Generic 을 지원하지 않기 때문에 형변환을 직접 해줘야한다
Event event = (Event)target;
if (event.getTitle() == null) {
// 여러 필드를 종합해서 봤을때 reject()
// 특정 필드에 관해서 rejectValue()
errors.rejectValue("title","notEmpty","title이 비어있습니다.");
}
}
}
errors.hasErrors() = true
----error code-----
notEmpty.event.title
notEmpty.title
notEmpty.java.lang.String
notEmpty
e.getDefaultMessage() = title이 비어있습니다.
Spring Boot 2.0.5 버전 이상 사용할 때
- LocalValidatorFactoryBean 빈으로 자동으로 등록이 된다
- JSR-380(Bean Validation 2.0.1) 구현체로 hibernate-validator를 사용한다
Spring 5에서 Bean Validation 2.0을 사용하려면
- 아래를 의존으로 추가해준다
<!-- validation-api는 생략 가능 -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.7.Final</version>
</dependency>
LocalValidatorFactoryBean
- Spring이 제공해주는
LocalValidatorFactoryBean
을 자동으로 빈 등록을 해준다
Bean Annotation을 아무런 빈을 등록 안했지만 자동으로 빈 등록이 된다 - AppRunner에서
LocalValidatorFactoryBean
으로 주입해도 무방하다.
@Autowired
Validator validator;
@Autowired
LocalValidatorFactoryBean validator1;
- 검증하려면 Bean Validation 애노테이션을 붙여준다
import javax.validation.constraints.Email;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
public class Event {
Integer id;
@NotEmpty
String title;
@NotNull @Min(0)
Integer limit;
@Email
String email;
public String getTitle() {
return title;
}
public void setLimit(Integer limit) {
this.limit = limit;
}
public void setEmail(String email) {
this.email = email;
}
}
@NotEmpty
- 빈 값 여부
@Min
- 최소값
@Email
- 이메일 주소 형식
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import java.util.Arrays;
@Component
public class AppRunner implements ApplicationRunner {
@Autowired
Validator validator;
@Autowired
LocalValidatorFactoryBean validator1; // 이것으로 사용해도 됩니다
@Override
public void run(ApplicationArguments args) throws Exception {
//어떤 validator가 출력되는지 실행
System.out.println(validator.getClass());
//고의적으로 에러를 내기 위한 set
Event event = new Event();
event.setLimit(-1);
event.setEmail("asdf");
// 검증할 Event 객체를 전달하여 Errors 인스턴스를 생성한다
Errors errors = new BeanPropertyBindingResult(event, "event");
validator.validate(event, errors);
// errors에 error 가 있는지 확인한다
System.out.println("errors.hasErrors() = "+errors.hasErrors());
// forEach로 에러코드와 기본 메세지 출력
errors.getAllErrors().forEach( e-> {
System.out.println("----error code-----");
Arrays.stream(e.getCodes()).forEach(System.out::println);
System.out.println("e.getDefaultMessage() = " + e.getDefaultMessage());
});
}
}
- 결과
class org.springframework.validation.beanvalidation.LocalValidatorFactoryBean
errors.hasErrors() = true
----error code-----
Email.event.email
Email.email
Email.java.lang.String
Email
e.getDefaultMessage() = 이메일 주소가 유효하지 않습니다.
----error code-----
Min.event.limit
Min.limit
Min.java.lang.Integer
Min
e.getDefaultMessage() = 반드시 0보다 같거나 커야 합니다.
----error code-----
NotEmpty.event.title
NotEmpty.title
NotEmpty.java.lang.String
NotEmpty
e.getDefaultMessage() = 반드시 값이 존재하고 길이 혹은 크기가 0보다 커야 합니다.
- 예상대로 title, limit, email 순으로 3개의 필드에서 에러가 발생했다.
각 에러가LocalValidatorFactoryBean
이 제공하는 에러코드와 메세지 기본값을 출력해준다
결론
- Bean Validation 애노테이션으로 검증할 수 있는 것들을
LocalValidatorFactoryBean
을 주입하여 충분히 validation 처리가 가능해진다. - 상황에 따라서 validation을 해야하는지 Validator를 구현해야 하는지 상황에 맞게 사용하면된다
References
- 해당 포스팅은 백기선님의 인프런 - 스프링 프레임워크 핵심 기술 강의를 보고 정리한 자료입니다.
'Spring > Spring Core' 카테고리의 다른 글
[Spring] AOP(Aspect-Oriented Programming) (0) | 2020.11.06 |
---|---|
[Spring] SpEL(Spring Expression Language) (0) | 2020.11.05 |
[Spring] 데이터 바인딩 추상화 - Converter, Formatter (0) | 2020.11.05 |
[Spring] 데이터 바인딩 추상화 - PropertyEditor (0) | 2020.11.05 |
[Spring] Resource 추상화 (0) | 2020.11.04 |