이전 글로 Validator로 분리하여 Controller의 로직과 Validation을 분리하는 방법을 알았고 MyValidator도 만들어 보았습니다.
Validation 검증 - Validator 이해와 MyValidator 만들어보기
이전 글로 FieldError를 사용하여, 사용자 입력 보존과 오류 메시지 남기기를 알아보았습니다. Validation 검증 - 사용자 입력 보존과 오류 메시지 남기기 이전 글로 Validation 오류를 담는 객체인 BindingR
hhhhicode.tistory.com
이번 글로 Validator를 사용하는 2가지 방법에 대해 알아보겠습니다.
Validator를 사용하는 2가지 방법
- MyValidator 직접 호출하기
- WebDataBinder를 통해서 호출하기
에 대해 다룹니다.
MyValidator 직접 호출하기
우선 MyValidator를 스프링 컨테이너에서 DI 받습니다.
@Slf4j
@Controller
@RequestMapping("/members")
public class MemberController {
private final MemberService memberService;
private final MyValidator myValidator;
public MemberController(MemberService memberService, MyValidator myValidator) {
this.memberService = memberService;
this.myValidator = myValidator;
}
...
}
스프링 컨테이너는 빈들을 싱글톤으로 관리해줍니다.
MyValidator는 MemberAddDto에 대해 공통으로 사용되는 것이니
싱글톤으로 관리되는 것이 좋다고 생각합니다.
이후 DI 받은 MyValidator를 Handler 안에서 사용해봅시다.
@PostMapping("/add")
public String testMyValidatorHandler(@ModelAttribute MemberAddDto memberAddDto, BindingResult bindingResult) {
myValidator.validate(memberAddDto, bindingResult);
if (bindingResult.hasErrors()) {
return "members/add";
}
// Validation 통과 시 로직
...
}
간단합니다.
종합적으로 보면 Validator의 validate 메서드를 사용하여 BindingResult 객체에 오류를 담습니다.
그리고 BindingResult가 hasErrors이면 Validation 오류 처리를 하고, 없다면 Validation 통과 처리를 하면 됩니다.
하나씩 살펴보겠습니다.
validate는 인자로 Validation을 적용해볼 인스턴스인 memberAddDto와
Validation 오류를 담을 인스턴스인 bindingResult를 가집니다.
validate는 memberAddDto가 지원되는 클래스의 인스턴스라면 validate를 시행할 것입니다.
Validation 오류가 있다면,
validate 내부에서 reject()나 rejectValue()로 메시지를 설정하며 FieldError나 ObjectError가
인자로 주입된 bindingResult에 담기게 됩니다.
그리곤 void를 반환하며 끝나게 됩니다.
다시 Handler로 돌아와서 BindingResult의 객체에 Validation 오류가 담겨있는지 없는지를 확인해보고
상황에 따라 처리 로직을 수행하면 됩니다.
WebDataBinder를 통해서 호출하기
스프링이 Validator Interface를 별도로 제공하는 이유는
체계적인 검증 기능을 도입하기 위해서입니다.
Validator를 직접 호출해서 사용하면 스프링의 여러 자동화를 제공받을 수 없습니다.
Validator를 WebDataBinder를 통해서 호출한다면
스프링이 여러 부분을 자동화해줍니다.
WebDataBinder
WebDataBinder는 스프링 파라미터 바인딩 역할을 수행하고
검증 기능도 내부에 포함합니다.
WebDataBinder를 통해서 Validator를 등록하고 사용해보겠습니다.
WebDataBinder 사용하기
WebDataBinder에 Validator 추가하기
@InitBinder
public void init(WebDataBinder binder) {
binder.addValidators(myValidator);
}
@InitBinder 애노테이션을 선언하여
해당 Controller에만 적용되도록 하였습니다.
WebDataBinder의 addValidators()를 사용하여
만들어둔 Validator를 등록합니다.
@InitBinder로 해당 Controller에만 적용되도록 하는 게 아니라
글로벌 설정을 하려면 다른 곳에서 해야 합니다.
@EnableFeignClients @SpringBootApplication public class MembersApplication implements WebMvcConfigurer { public static void main(String[] args) { SpringApplication.run(MembersApplication.class, args); } @Override public Validator getValidator() { return new MyValidator(); } }
저는 글로벌 설정인만큼 보기 쉽게 @SpringBootApplication이 있는 곳에 글로벌 설정을 하였습니다.
WebMvcConfigurer Interface를 상속받으면 getValidator 메서드를 오버라이드 할 수 있습니다.
해당 메서드에서 Validator를 반환하도록 하면 됩니다.
@SpringBootApplication은 내부에 @Configuration을 가지고 있기 때문에
스프링 빈으로 등록하고 반환해도 됩니다.
@Validated 적용
Validator를 직접 호출하는 부분이 필요 없게 됩니다.
WebDataBinder를 통해 Validator는 등록되어 있기 때문에
Validation 대상 객체 앞에 @Validated를 붙이기만 하면 됩니다.
@PostMapping("/add")
public String testMyValidatorHandler(@Validated @ModelAttribute MemberAddDto memberAddDto, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "members/add";
}
//Validation 통과 시 로직
...
}
Validator를 직접 호출하는 코드가 깔끔하게 사라졌습니다.
우리는 직접 Validator를 사용할 때,
Validation 대상이 되는 객체와 BindingResult 객체가 필요했었습니다.
가만 보면 @Validated를 사용하는 코드에서도 해당 부분은 모두 존재합니다.
Handler의 파라미터는 ArgumentResolver가 적용되어 인자를 넣어주는 것을 보면 알듯이
스프링이 관리하는 부분입니다.
그러므로 파라미터에 애노테이션과 사용되는 객체를 룰에 맞게 적어주기만 하면
스프링이 여러 부분을 자동화해주어, 이렇게 간편하고 보기 좋게 Validation을 적용할 수 있게 되는 것입니다.
참고로
검증시 @Validated, @Valid 둘 다 사용 가능합니다.
javax.validation.@Valid를 사용하려면
build.gradle 의존관계 추가가 필요합니다.
implementation 'org.springframework.boot:spring-boot-starter-validation' @Validated는
의존관계 추가가 필요하지 않습니다.
@Valid는 자바 표준 검증 애노테이션이고 @Validated는 스프링 전용 검증 애노테이션입니다.
동작 방식
@Validated는 Validator를 실행하도록 하는 애노테이션입니다.
@Validated가 붙으면
WebDataBinder에 등록한 Validator를 찾아서 실행합니다.
@Validated가 WebDataBinder에 등록된 옳바른 Validator를 찾는 기준은
Validator의 supports() 메서드 입니다.
@Override
public boolean supports(Class<?> clazz) {
return MemberAddDto.class.isAssignableFrom(clazz);
}
그래서 대상 객체 앞에 @Validated를 적어두었을 때
여러 Validator가 등록되어있더라도 올바른 Validator를 찾아서 실행시킬 수 있습니다.
동작 순서
1. 글로벌 설정을 하지 않았을 시
@InitBinder에 의해 사용자 요청이 올 때마다 WebDataBinder가 만들어지고
Validator가 WebDataBinder에 addValidators(Validator) 됩니다.
2. @ModelAttribute로 인해 MemberAddDto 객체에 데이터가 하나하나 바인딩됩니다.
바인딩 오류가 발생하면 해당 Field에 대해 FieldError를 생성해서 BindingResult에 넣어줍니다.
그리고 나머지 다른 Field는 다시 바인딩을 시도합니다.
3. @Validated로 인해 WebDataBinder에 등록된 Validator들을 돌면서
supports()를 기준으로 대상 객체에 지원되는 Validator를 찾습니다.
4. 지원되는 Validator를 찾으면
Validator의 validate()를 수행하면서 대상 객체를 검증하고
생성된 Validation 오류들을 reject(), rejectValue()를 통해서 메시지를 설정하여
ObjectError와 FieldError를 생성하여 BindingResult에 담습니다.
5. 다시 Controller로 돌아와서
BindingResult에 담겨있는 Validation 오류를 확인하고 추가 처리를 합니다.
다음 글로 Bean Validation에 대해 간단하게 알아보겠습니다.
Validation 검증 - Bean Validation 이란?
이전 글로 Validator를 사용하는 2가지 방법에 대해 알아보았습니다. Validation 검증 - Validator를 사용하는 2가지 방법 이전 글로 Validator로 분리하여 Controller의 로직과 Validation을 분리하는 방법을 알았
hhhhicode.tistory.com
'Spring MVC > Validation' 카테고리의 다른 글
Validation 검증 - 도메인 객체를 하나만 사용할 경우 문제점 해결 (0) | 2022.12.09 |
---|---|
Validation 검증 - Bean Validation의 글로벌 Validation 오류 (2) | 2022.12.08 |
Validation 검증 - Bean Validation과 Spring MVC (1) | 2022.12.06 |
Validation 검증 - Bean Validation 이란? (1) | 2022.12.06 |
Validation 검증 - Validator 이해와 MyValidator 만들어보기 (1) | 2022.12.04 |
Validation 검증 - 사용자 입력 보존과 오류 메시지 남기기 (1) | 2022.12.03 |
Validation 검증 - BindingResult 사용 (1) | 2022.12.02 |
Validation 검증 - BindingResult란? (1) | 2022.12.02 |
댓글