쌓고 쌓다
Invalid target for Validator 에러 고찰 본문
어쩌다 직면한 이 에러에 오늘 하루 다 보냈다...ㅎㅎ
먼저 상황을 보자.
에러 발생 상황
컨트롤러에 작성된 @InitBinder, @Validated를 통해 유효성 검사를 실시한다.
public class ValidationItemControllerV2 {
private final ItemValidator itemValidator;
@InitBinder
public void init(WebDataBinder dataBinder) {
dataBinder.addValidators(itemValidator);
}
@PostMapping("/add")
public String addItemV5(@Validated @ModelAttribute Item item, BindingResult bindingResult, Model model) {
if(bindingResult.hasErrors()) {
model.addAttribute("errors", bindingResult);
return "validation/v2/addForm";
}
}
일부러 유효성 검사에 실패하게 폼 데이터를 전송했고 아래의 에러가 발생!
중요한 부분만 보면
java.lang.IllegalStateException: Invalid target for Validator 에러이며
org.springframework.validation.BeanPropertyBindingResult 를 보여주었다.
에러 발생 원인
에러의 발생지인 DataBinder.java의 578번 라인으로 타고 들어가보자.
Validator의 supports 메서드가 거짓을 반환하면 Exception을 던지게 되어있다... 이게무슨...
지원하지 않으면 그걸로 검증을 안하고 넘어가면 될것을 굳이 예외를 던져버린다...
아까 보여준
org.springframework.validation.BeanPropertyBindingResult 에서
BeanPropertyBindingResult를 검증하는 과정에서 supports 메서드가 거짓을 반환하여
"Invalid target for Validator" 예외를 던져준것이다.
BeanPropertyBindingResult? 이게 뭘까?
BindingResult는 인터페이스다. 이 인터페이스의 구현체 클래스가 BeanPropertyBindingResult이다.
그래서 이것을 검증할때 예외가 터져버린것이다.
@Component
public class ItemValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
if(BeanPropertyBindingResult.class.isAssignableFrom((clazz))
||Item.class.isAssignableFrom(clazz))
return true;
return false;
}
...
}
구현한 검증기인 ItemValidator의 오버라이드한 supports 메서드에
BeanPropertyBindingResult 클래스에 대해 검증을 지원한다고 정의해두면
Invalid target for Validator 에러는 터지지 않는다.
이제 문제의 원인을 파악했다...!
여기서 짚고 넘어가야할 부분이 있다.
@Validated를 작성하지 않은 파라미터는 검증기를 왜 거친걸까?
이것저것 연구해본 결과 해당 컨트롤러의 실제 사용하는 파라미터에 대해 모두 검증기를 거친다.
아니~ 그러면 @Validated를 붙이던말던 다 검증기를 거치면 안써도 되는거 아닌가~?
@Component
public class ItemValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
System.out.println("ItemValidator.supports");
System.out.println("clazz = " + clazz.getName());
...
}
@Override
public void validate(Object target, Errors errors) {
System.out.println("ItemValidator.validate");
}
}
조금 로그를 찍어보면 어노테이션 적용 유무의 차이를 알 수 있다.
현재 컨트롤러 상황은 item 객체 검증기를 거치고 BindingResult까지 검증기를 거친다는걸 염두해두고 보자.
드래그한 영역은 item 객체가 검증기를 거치는 과정이고, 아닌 부분은 BindingResult의 부분이다.
정리하자면
- @Validated를 작성하면 : supports와 validate 메서드 모두 수행
- @Validated를 작성하지 않으면 : supports 메서드만 수행
즉 @Validated를 붙여줘야지 supports는 물론 실제 유효한지 검사하는 로직이 들어있는 validate까지 수행한다.
이제 원인과 보너스인 @Validated 붙이고 안붙이고 차이를 알았으니 해결방법 알아보자.
에러 해결 방법
@InitBinder("item")
public void init(WebDataBinder dataBinder) {
dataBinder.addValidators(itemValidator);
}
@InitBidner에 검증할 모델 객체의 이름을 주어 해당 모델 객체만 검증기를 거치도록 하자.
이렇게 이름을 주지 않으면 모든 모델 객체에 대해 검증기를 거치고 supports에 거짓이 뜨면 예외가 터져버린다 ㅠ,ㅠ
+ 추가
@InitBinder
public void init(WebDataBinder dataBinder) {
log.info("init binder = {}", dataBinder);
dataBinder.addValidators(itemValidator);
}
이렇게 이름을 지정해주지 않고 사용한 상황에서
@Validated를 한번 사용했고 그에 검증을 한번만 수행할 것 같지만
로그를 찍어보면 여러번 출력이 찍힌다.
위에서 설명한 이유와 동일한데 @Validated를 적어준 모델 객체에만 검증기를 거치는게 아니기 때문에
출력이 여러번 나올 수 있다.
'프로그래밍 > spring' 카테고리의 다른 글
No HttpMessageConverter for java.util.HashMap and content type "application/x-www-form-urlencoded" (0) | 2023.09.01 |
---|---|
typeMismatch 처리와 에러 코드 관리 - 26 (0) | 2023.09.01 |
properties의 한글 값 ??로 출력되는 문제 해결방법 (0) | 2023.08.30 |
BindingResult rejectValue(), reject() (0) | 2023.08.30 |
MessageCodesResolver (0) | 2023.08.30 |