쌓고 쌓다

Invalid target for Validator 에러 고찰 본문

프로그래밍/spring

Invalid target for Validator 에러 고찰

승민아 2023. 8. 31. 21:18

어쩌다 직면한 이 에러에 오늘 하루 다 보냈다...ㅎㅎ

 

먼저 상황을 보자.

에러 발생 상황

컨트롤러에 작성된 @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를 적어준 모델 객체에만 검증기를 거치는게 아니기 때문에

출력이 여러번 나올 수 있다.

Comments