• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    해결됨

@InitBinder와 @Validated 사용 시 문제 IllegalStateException: Invalid target for Validator

21.08.10 11:31 작성 조회수 850

7

검증 시 @InitBinder 를 사용해서 Validator 를 추가했을 때, 강의 예제와 같은 상황에선 문제가 없습니다. 하지만, 너무 햇갈리는 상황이 일어나서 질문드립니다.

컨트롤러의 매핑 메서드의 인자에 @Validated 애노테이션을 추가하지 않더라도  매핑 메서드에서 model.addAttribute(); 에 Item 이 아닌 객체를 넣을 시 다음과 같은 오류가 발생합니다..

java.lang.IllegalStateException: Invalid target for Validator

======

추가.

우선 디버깅을 하다보니 @Validated 를 붙이지 않아도 무엇이든지 값을 매핑해야할 일이 있다면 @InitBinder 를 통해 등록한 Validator 의 support() 가 호출 된다는 것을 알 수 있었습니다.

근데, 이걸 보고나니 더욱 의문이 남는게 Validator의  support() 를 실행했을 때 false 가 나와서 해당 검증기를 지원하지 않는다고 하면 그냥 검증을 안하고 넘어가는게 아닌가봅니다..잘 이해가 가지 않습니다.

결국 Model 객체에다가 Item이 아닌 다른 객체를 넣을 때, 결과적으로 DataBinder.java 안의  assertValidators(Validator ... validators); 메서드에서 

if (validator != null && (target != null && !validator.supports(target.getClass()))) {
	throw new IllegalStateException("Invalid target for Validator [" + validator + "]: " + target);
}

조건이 참이되버려서 지원하지 않는다면 그냥 예외가 터져버리고 맙니다.

이런식으로 된다면 @InitBinder 로 검증기를 등록해서 사용하다간,  되려 예측하지 못한 검증찾기 실패 오류만 늘어나는것이 아닌지 생각됩니다...

한 컨트롤러내에서 Model 객체에 Item 객체 말고도 실제론 다양한 객체를 넣을텐데 이런 경우엔 @InitBinder 를 사용하지 않고 전부 수동으로 메서드에서 검증을 해야하는건가요?

해결 방법이 궁금합니다..

마지막으로 해당 상황을 재현할 수 있는 샘플코드를 작성해보았습니다..

뭔가 질문이 난잡한거 같아서 죄송합니다..ㅠ 항상 감사드립니다.

MyController.java

@Controller
@RequiredArgsConstructor
public class MyController {
private final MyValidator myValidator;

@InitBinder
public void init(WebDataBinder webDataBinder) {
webDataBinder.addValidators(myValidator);
}

@GetMapping("/index/{text}")
public String myView(@PathVariable String text, Model model) {
SomeObject someObject = new SomeObject();
someObject.setText(text);
model.addAttribute("someObject", someObject);
return "myView";
}

@GetMapping("/index")
public String index(@ModelAttribute TargetObject targetObject, Model model) {
model.addAttribute("targetObject", targetObject);
return "myView";
}
}

MyValidator.java

@Component
public class MyValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return clazz.isAssignableFrom(TargetObject.class);
}

@Override
public void validate(Object target, Errors errors) {
TargetObject myTarget = (TargetObject) target;
if (myTarget.getText().equals("error")) {
errors.reject("error");
}
}
}

SomeObject.java

@Data
public class SomeObject {
private String text;
}

TargetObject.java

@Data
public class TargetObject {
private String text;
}

답변 1

답변을 작성해보세요.

10

안녕하세요. irostub님

일반적으로 컨트롤러를 만들 때 하나의 컨트롤러는 하나의 모델 객체(Command 객체)를 사용하기 때문에 이렇게 사용해도 큰 이슈가 없습니다.

하지만 여러 모델 객체를 사용하고 싶으시면 다음과 같이 적용하시면 됩니다.

@InitBinder("targetObject")
public void initTargetObject(WebDataBinder webDataBinder) {
log.info("webDataBinder={}, target={}", webDataBinder, webDataBinder.getTarget());
webDataBinder.addValidators(/*TargetObject 관련 검증기*/);
}

@InitBinder("sameObject")
public void initSameObject(WebDataBinder webDataBinder) {
log.info("webDataBinder={}, target={}", webDataBinder, webDataBinder.getTarget());
webDataBinder.addValidators(/*SameObject 관련 검증기*/);
}

@InitBinder에 지금처럼 이름을 넣어주면 해당 모델 객체에만 영향을 줍니다. 반면에 이름을 넣지 않으면 모든 모델 객체에 영향을 줍니다.

만약 targetObject는 검증기를 사용하고, sameObject는 검증기를 사용하고 싶지 않다면 다음과 같이 하나만 명식적으로 적용하면 됩니다.

@InitBinder("targetObject")
public void initTargetObject(WebDataBinder webDataBinder) {
log.info("webDataBinder={}, target={}", webDataBinder, webDataBinder.getTarget());
webDataBinder.addValidators(/*TargetObject 관련 검증기*/);
}

감사합니다.