컨트롤러의 중요한 역할 중 하나는 HTTP 요청이 정상인지 검증하는 것이다.
클라이언트 검증은 HTTP를 POSTMAN과 같은 프로그램으로 조작할 수 있다. 그렇다고 서버만으로 검증하면 고객의 즉각적 사용성이 부족해진다. 따라서 두 개를 섞어서 사용하되 서버의 검증은 최종적으로 필수이다.
API 방식을 사용하면 스펙을 잘 정의해서 검증 오류를 API 응답 결과에 포함시켜주어야 한다.
검증 오류를 보관할 map 생성
Map<String, String> errors = new HashMap<>();
!StringUtils.hasText(상품.get이름())
다음과 같이 import org.springframework.util.StringUtils;를 import하고 이름이 비었는지 확인한다. 나머지 로직도 if문을 통해 검증 오류가 있다면 errors에 put을 한다.
if (!errors.isEmpty()) {
model.addAttribute("errors", errors);
return "입력폼주소";
}
다음과 같이 errors가 비어있지 않다면 같은 폼으로 되돌려 보낸다.
th:classappend="${errors?.containsKey('필드명')} ? '에러클래스명' : _"
th:if="${errors?.containsKey('필드명')}" th:text="${errors['필드명']}
다음과 같이 작성하면 된다.
여기서 errors?문법은 스프링의 SpringEL이 제공하는 문법인데 null일 때 NullPointerException이 발생하는 대신 null을 반환하게 해준다.
스프링이 제공하는 검증 오류 처리 방법이다. 컨트롤러 메서드에서 BindingResult 파라미터의 위치는 Model 바로 다음에 와야 한다.
BindingResult의 함수인 addError를 통해 오류가 있는 필드나 객체를 넣어줄 수 있다.
BindingResult bindingResult를 파라미터로 받고
bindingResult.addError(new FieldError(...));
bindingResult.addError(new ObjectError(...));
두 가지 생성자
public FieldError(String objectName, String field, String defaultMessage);
public FieldError(String objectName, String field, @Nullable Object rejectedValue, boolean bindingFailure, @Nullable String[] codes, @Nullable Object[] arguments, @Nullable String defaultMessage)
파라미터 목록
objectName : 오류가 발생한 객체 이름
field : 오류 필드
rejectedValue : 사용자가 입력한 값(거절된 값)
bindingFailure : 타입 오류 같은 바인딩 실패인지, 검증 실패인지 구분 값
codes : 메시지 코드
arguments : 메시지에서 사용하는 인자
defaultMessage : 기본 오류 메시지
생성자
public ObjectError(String objectName, String defaultMessage) {}
특정 필드를 넘어서는 오류가 있으면 ObjectError 객체를 생성해서 bindingResult 에 담아두면 된다.
objectName : @ModelAttribute 의 이름
defaultMessage : 오류 기본 메시지
타임리프는 스프링의 BindingResult를 활용해서 편리하게 검증 오류를 표현하는 기능을 제공한다.
#fields로 BindingResult가 제공하는 검증 오류에 접근할 수 있다.
th:errors: 해당 필드에 오류가 있는 경우에 태그를 출력.
th:errorclass: th:field에서 지정한 필드에 오류가 있으면 class정보를 추가한다.
FieldError를 통해 잘못된 타입값이 들어와도 컨트롤러가 호출된다. 또한 해당 오류를 BindingResult에 담아서 컨트롤러를 호출하여 사용자가 입력한 오류값을 저장하고 th:field에서 그 오류값을 보여준다.
참고로 BindingResult는 Errors인터페이스를 상속받고 있는 인터페이스다. 관례상 BindingResult를 많이 사용한다.
BindingResult 가 제공하는 rejectValue() , reject() 를 사용하면 FieldError , ObjectError 를 직접 생성하지 않고, 깔끔하게 검증 오류를 다룰 수 있다.
rejectValue()
void rejectValue(@Nullable String field, String errorCode, @Nullable Object[] errorArgs, @Nullable String defaultMessage);
reject()
void reject(String errorCode, @Nullable Object[] errorArgs, @Nullable String defaultMessage);
field : 오류 필드명
errorCode : 오류 코드
errorArgs : 오류 메시지에서 {0} 을 치환하기 위한 값
defaultMessage : 오류 메시지를 찾을 수 없을 때 사용하는 기본 메시지
MessageCodesResolver 인터페이스이고 DefaultMessageCodesResolver 는 기본 구현체이다.ObjectError , FieldError객체 오류
객체 오류의 경우 다음 순서로 2가지 생성
1.: code + "." + object name
2.: code
예) 오류 코드: required, object name: item
1.: required.item
2.: required
필드 오류
필드 오류의 경우 다음 순서로 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"
기본적으로 직접 validator를 만들기 위해서는
@Component를 통해 빈 등록을 하고 implements Validator를 통해 supports와 validate함수를 @Override해야 한다.
supports함수는 나중에 @Validated나 @Valid가 붙어있는 객체에 어느 validator를 사용할 지 확인하는 용도이며 validate함수는 실제로 검증을 하는 로직이 들어있는 함수다.
해당 어노테이션은 파라미터에서 모델 객체 앞에 붙여주면 된다. 예를 들어 Item이란 객체를 파라미터로 받을 때는 @Validated @ModelAttribute Item item과 같은 식이다.
해당 내용은 다음 포스터에 기재할 예정이다.