컨트롤러의 중요한 역할 중 하나는 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
과 같은 식이다.
해당 내용은 다음 포스터에 기재할 예정이다.