크게 타입검증, 필드 검증, 특정 필드의 범위를 넘어서는 검증 3가지의 요구사항이 추가된 상황
=> 웹 서비스는 폼 입력 시 오류가 발생하면 고객이 입력한 데이터를 유지한 상태로 어떤 오류가 발생했는지 알려주어야 한다.
컨트롤러의 중요한 역할중 하나는 HTTP 요청이 정상인지 검증하는 것이다.
참고) 클라이언트, 서버 검증
Map<String, String> errors = new HashMap<>();
if (!StringUtils.hasText(item.getItemName())) {
errors.put("itemName", "상품 이름은 필수입니다.");
}
<div th:if="${errors?.containsKey('globalError')}">
<p class="field-error" th:text="${errors['globalError']}">전체 오류 메시지</p>
</div>
참고 ) Safe Navigation Operator
errors?. 은 errors 가 null 일때 NullPointerException 이 발생하는 대신, null 을 반환하는 문법이다BindingResult 가 있으면 @ModelAttribute 에 데이터 바인딩 시 오류가 발생해도 BindingResult에 정보를 담아 컨트롤러가 호출된다! @PostMapping("/add")
public String addItemV1(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
public FieldError(String objectName, String field, String defaultMessage) {}
FieldError 객체를 생성해서 bindingResult에 담아둔다.public ObjectError(String objectName, String defaultMessage) {}
ObjectError 객체를 생성해서 bindingResult에 담아둔다.BindingResult 는 인터페이스이고, Errors 인터페이스를 상속받고 있다BeanPropertyBindingResult 라는 것인데, 둘다 구현하고 있으므로 BindingResult 대신에 Errors 를 사용해도 된다Errors 인터페이스는 단순한 오류 저장 및 조회 기능 제공, BindingResult는 addError() 등의 기능을 제공FieldError와 ObjectError는 각각 두 가지 생성자를 제공하고 있다.
new FieldError("item", "price", item.getPrice(),false, null, null, "가격은 1,000 ~ 1,000,000 까지 허용합니다."));
FieldError와 ObjectError에는 메시지 코드와 인자 값을 받는 생성자가 존재
application.properties 파일에 spring.messages.basename=messages,errors 설정을 추가
FieldError와 ObjectError는 다루기 너무 번거롭다.
BindingResult는 이미 본인이 검증해야 할 객체인 target을 알고 있다.
=> rejectValue(), reject() 함수를 통해 기존 코드를 단순화 !
void rejectValue(@Nullable String field, String errorCode, @Nullable Object[] errorArgs, @Nullable String defaultMessage);
MessageCodesResolverDefaultMessageCodesResolver 가 스프링에 기본적으로 등록되어 있음ObjectError, FieldError 와 함께 사용된다. rejectValue() , reject() 는 내부에서 MessageCodesResolver 를 사용한다. MessageCodesResolver 를 통해서 생성된 순서대로 오류 코드를 보관한다. DefaultMessageCodesResolver의 경우
객체 오류의 경우 다음 순서로 2가지 생성
1.: code + "." + object name
2.: code
필드 오류의 경우 다음 순서로 4가지 메시지 코드 생성
1.: code + "." + object name + "." + field
2.: code + "." + field
3.: code + "." + field type
4.: code
예) 오류 코드: typeMismatch, object name "user", field "age", field type: int
1. "typeMismatch.user.age"
2. "typeMismatch.age"
3. "typeMismatch.int"
4. "typeMismatch"
MessageCodesResolver 는 required.item.itemName 처럼 구체적인 것을 먼저 만들어주고, required 처럼 덜 구체적인 것을 가장 나중에 만든다
크게 중요하지 않은 메시지는 범용성 있는 requried 같은 메시지로 끝내고, 정말 중요한 메시지는 꼭 필요할 때 구체적으로 적어서 사용하는 방식이 더 효과적
가장 낮은 단계까지 오류 코드를 찾지 못한 경우 => 코드에 작성한 디폴트 메시지를 사용한다
타입 오류의 경우도 errors.properties 에 등록해두면 오류 메시지를 커스텀할 수 있다.
Empty, 공백 같은 단순한 기능의 경우 ValidationUtils를 통해 간단히 처리가 가능하다.
ValidationUtils.rejectIfEmptyOrWhitespace(bindingResult, "itemName",
"required");
public interface Validator {
boolean supports(Class<?> clazz);
void validate(Object target, Errors errors);
}
그러면 이 검증기를 어떻게 활용할까?
가장 단순한 방법으로는 해당 Validator를 직접 호출해서 사용하는 방식이 있다.
=> 이러면 Validator를 굳이 상속받지 않아도 됨
Validator를 사용하는 두번째 방법
스프링이 Validator 인터페이스를 별도로 제공하는 이유는 체계적으로 검증 기능을 도입하기 위해서다. WebDataBinder를 사용하면 스프링의 추가적인 도움을 받을 수 있다.
스프링의 파라미터 바인딩의 역할을 해주고 검증 기능도 내부에 포함한다
@InitBinder
public void init(WebDataBinder dataBinder) {
log.info("init binder {}", dataBinder);
dataBinder.addValidators(itemValidator);
}
@PostMapping("/add")
public String addItemV6(@Validated @ModelAttribute Item item, BindingResult
bindingResult, RedirectAttributes redirectAttributes) {
@InitBinder를 통해 해당 컨트롤러에서 사용하는 Validator를 등록하고, 검증 대상 앞에 @Validated 어노테이션을 붙인다.동작 방식
@Validated 는 검증기를 실행하라는 애노테이션이다
이 애노테이션이 붙으면 앞서 WebDataBinder 에 등록한 검증기를 찾아서 실행한다. 그런데 여러 검증기를 등록한다면 그 중에 어떤 검증기가 실행되어야 할지 구분이 필요하다. 이때 supports() 가 사용된다.