🌱
Bean Validation
March 16, 2024
Bean Validation
Spring Framework에서 제공하는 Validator를 사용하여 로직을 분리하는 방법도 있지만, 본 포스팅에서는 더 편리하고 자주 사용되는 방법인 Bean Validation에 대해 다룬다.
Bean Validation 관련 라이브러리 추가가 필요하다.
예제 코드
환경 설정
- errors.properties 생성
spring.application.name=ValidationExample
spring.messages.basename=errors
spring.messages.basename
에 errors 추가하여 errors.properties 등록
User 클래스
@Data
public class User {
@NotNull
@Min(1)
@Max(100)
private final int age;
@NotNull
private final String name;
}
NotNull
,NotEmpty
,NotBlank
의 차이는 다음과 같다.NotNull
: null값을 허용하지 않는다.NotEmpty
: null값과 ""(초기화된 String)을 허용하지 않는다.NotBlank
: null값과 "", ” “를 모두 허용하지 않는다.
NotNull(message = "이름은 null일 수 없습니다")
와 같이 디폴드 메세지를 지정 가능하다.- 추가로
@Range
어노테이션도 있는데, 이는 Hibernate에서 제공한다.
UserController 클래스
@RestController
@RequiredArgsConstructor
public class userController {
private final MessageSource messageSource;
@PostMapping("/test")
public ResponseEntity<Object> userEcho(@Validated @RequestBody User user, BindingResult bindingResult) {
// 특정 필드가 아닌 복합 룰 검증
if(user.getName().equals("Dennis Ritchie") && user.getAge() > 70)
{
bindingResult.rejectValue(null, "InvalidAge", new Object[]{user.getName(), 70}, null);
}
// 에러 발생 시 BadRequest
if(bindingResult.hasErrors()) {
String errMsg;
if(bindingResult.getFieldError() != null) { // 필드 에러가 있다면 우선적으로 보여준다.
errMsg = messageSource.getMessage(bindingResult.getFieldError(), LocaleContextHolder.getLocale());
return ResponseEntity.badRequest().body(errMsg);
}
else { // 필드 에러가 없다면 오브젝트 에러를 반환한다.
errMsg = messageSource.getMessage(bindingResult.getGlobalError(), LocaleContextHolder.getLocale());
return ResponseEntity.badRequest().body(errMsg);
}
}
return ResponseEntity.ok(user);
}
}
- Spring에서 자동으로 빈을 등록해주는
MessageSource
를 Autowired해주기 위해@RequiredArgsConstructor
사용 - 인자로 받는
User
에@Validated
어노테이션을 추가하여 검증 수행 - 인자로
BindingResult
를 추가하여 오류 발생시에도 함수가 정상적으로 동작BindingResult
는Errors
를 상속하는 인터페이스- 스프링에의해 자동으로 구현체가 생성됨
- 필드 에러가 아닌 경우 복합룰의 경우에는 직접 코드로 검증하는 것이 더 나음
- 에러가 여러개인 경우 모두 보여주고 싶다면 루프문 추가 필요.
- 에러 메세지를 어떻게 긁어오는지 확인하고 싶다면 errors의 code를 확인한다.
errors.properties
Min={1} 이상이어야함!!!
Min.age=나이는 {1} 이상이어야함!!!
InvalidAge={0}의 나이는 {1}을 넘을 수 없음
- {}안의 내용은 arguments로 들어감.
@Min(1)
의 1의 경우가 arguments의 예시. 자동으로 매핑해준다.
- 에러 메세지를 가져오는 우선순위는 다음과 같다.
- 먼저 코드를 확인함, 더 세부적인게 존재하면 세부적인 것을 선택.
- 우선순위를 정하는 기준은 다음과 같다.
객체 오류
객체 오류의 경우 다음 순서로 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"
검증 순서
- @ModelAttribute 각각의 필드에 타입 변환 시도
- 성공하면 다음으로
- 실패하면 typeMismatch 로 FieldError 추가
- Validator 적용
위 얘시 코드는
@ModelAttribute
가 아닌@RequestBody
를 사용하고 있기 때문에 typeMismatch 검출을 위해서는@ExceptionHandler(TypeMismatchException.class)
를 정의해줘야 한다.@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(TypeMismatchException.class) public ResponseEntity<Object> handleTypeMismatch(TypeMismatchException ex) { String errorMsg = "Invalid input type for request body"; return ResponseEntity.badRequest().body(errorMsg); } }
@ControllerAdvice
는@Component
어노테이션의 특수한 케이스로, 스프링 부트 애플리케이션에서 전역적으로 예외를 핸들링할 수 있게 해주는 어노테이션이다.- 기본적으로
ExceptionHandler
는 해당 컨트롤러에서만 동작하나,@ControllerAdvice
에 정의하면 전역으로 사용 가능.
MessageCodesResolver
스프링은 MessageCodesResolver
로 오류 매세지의 우선순위를 정하는 기능을 지원한다.
- Validation 프로세스에서 MessageCodesResolver는 오류에 대한 메시지 코드를 생성한다.
- 이 메시지 코드는 MessageSource에 전달되어 실제 메시지로 변환된다.
- 따라서 MessageCodesResolver는 메시지 코드를 생성하는 역할을 담당하고, MessageSource는 이를 실제 메시지로 변환하여 사용자에게 제공한다.
테스트
승인
거절