[SPRING] BindingResult을 활용한 Validation처리
스프링MVC의 컨트롤러에서는 클라이언트로부터 받은 요청이 정상적인지 데이터는 형식에 맞는지에 대한 검증
해야 하는데 스프링이 제공하는 BindingResult을 활용하면 좀 더 편리하게 처리가 가능하다.
BindingResult의 학습내용을 정리합니다.
Project Metadata
- project : Gradle Project
- Language : Java 11
- Framework : Spring Boot 2.5.x
- Template Engine : Thymeleaf
BindingResult
@PostMapping("/test")
public String userPage(@ModelAttribute User user, BindingResult bindingResult) {
if (!StringUtils.hasText(user.getUserId())) {
bindingResult.addError(new FieldError("user", "userId", user.getUserId(), false, new String[]{"essential.user.userId"}, null, null));
}
if (user.getPassword().equals(user.getUserId())) {
bindingResult.addError(new ObjectError("user", new String[]{"equal.user"}, null, null));
}
log.info("bindingResult = {}", bindingResult);
return "userPage";
}
BindingResult의 주요 특징
- 파라미터 위치는 검증 대상의 다음에 위치해 있어야 한다.
- BindingResult 는 Model에 자동으로 포함된다.
- 바인딩타입 오류에도 유연하게 대응할 수 있다.
FieldError
대상 객체에 있는 필드의 에러일 경우 FieldErorr를 선택한다.
파라미터 항목
- objectName : 검증대상의 객체 이름
- field : 오류 필드명
- rejectedValue : 클라이언트의 입력 데이터(입력 데이터 유지에 사용)
- bindingFailure : 바인딩 에러 여부 -> 직접 선언 시에는 바인딩 에러가 아니므로 false
- codes : MessageSource의 메시지코드(배열로 전달 시 가장 상위에 있는 메시지 선택)
- arguments : MessageSource 파라미터 항목
- defaultMessage : 기본 메세지
ObjectError
대상 객체에 필드가 없는 에러 즉 특정 필드의 에러 사항이 아닌
검증이 필요한 조건에 의한 에러일 경우 사용
파라미터 항목
- objectName : 검증대상의 객체 이름
- codes[] : MessageSource의 메시지 코드(배열로 전달 시 가장 상위에 있는 메시지 선택)
- arguments[] : MessageSource 파라미터 항목(배열로 전달 시 가장 상위에 있는 메시지 선택)
- defaultMessage : 기본 메시지
현재 테스트 환경은 타임리프를 사용해 BindingResult에 접근하여 에러 메시지를 활용하지만 다른 템플릿 엔진이나
HTTP API 형식일 경우 BindingResult를 확인하여 필요한 데이터를 활용해야 한다.
MessageSource가 정리되어있고 FieldError , ObjectError의 선언이 번거롭다면 BindingResult의 rejectValue, reject
메서드를 활용할 수 있다.
rejectvalue, reject
Errors 인터페이스에 선언되어 있으며 errorcode 활용과 validation처리를 좀더 유연하게 할 수 있다.
@PostMapping("/test")
public String userPageRejectvalue(@ModelAttribute User user, BindingResult bindingResult) {
// Field Error
if(!StringUtils.hasText(user.getUserId())) {
bindingResult.rejectValue("userId", "essential"); //FieldName, errorcode
}
// Object Error
if(user.getPassword().equals(user.getUserId())) {
bindingResult.reject("equal", null, null); // errorcode, errorArgs ,defaultMessage
}
log.info("bindingResult = {}", bindingResult);
return "userPage";
}
FieldError, ObjectError와 다르게 errorCode가 단순화된 걸 볼 수 있는데
이는 스프링이 제공하는 MessageCodesResolver덕분이다.
MessageCodesResolver는 메시지 코드를 생성하는데 DefaultMessageCodesResolver가 기본 구현체이다.
DefaultMessageCodesResolver의 메세지 생성 규칙
필드 오류
1. errorcode + "." + object name + "." + field
2. code + "." + field
3. code + "." + field type(Object에 선언된 자료형)
4. code
ex ) codes [essential.user.userId, essential.userId, essential.java.lang.String, essential]
객체 오류
1. code + "." + object name
2. code
ex ) codes [equal.user, equal]
타입 오류
필드 오류와 동일하게 생성되며 errorcode가 typeMismatch로 입력된다.
rejectvalue, reject 메소드는 MessageCodesResolver를 통해 코드를 생성하고 내부에서
FieldErorr, ObjectError를 사용하여 생성된 코드를 배열로 전달한다.
바인딩 타입 오류 대응
필드의 타입이 숫자형인데 클라이언트에서 문자열이 전송된다면 데이터를 바인딩할 수 없어
컨트롤러 호출 안되고 400 오류가 발생한다.
BindingResult이 선언되어 있다면 타입 오류가 있어도 컨트롤러가 정상 호출되는데 bindingResult가 FieldError 생성하여
에러 내용을 bindingResult에 넣어준다.
정리
기본적으로 메시지 코드를 단계적으로 잘 작성해놔야 사용이 용이할 것으로 생각된다. 구체적에서 점차적으로 작성해놔야 범용적으로 사용이 기대될 것으로 생각된다. HTTP API 형식일 때 응답을 어떻게 처리해야 할지 생각해봐야겠다.