Bean Validation
Bean Validation은 JSR 기술 표준으로, 검증 애노테이션과 여러 인터페이스의 모음이다.
Validator (검증기)
스프링이 제공하는 검증용 객체이다. 개발자는 해당 인터페이스를 구현하여 클래스로 사용하면 되기 때문에 검증 로직과 비즈니스 로직을 구분할 수 있게 되고 체계적으로 검증 기능을 도입할 수 있게 된다.
public interface Validator {
boolean supports(Class<?> clazz);
void validate(Object target, Errors errors);
}
해당 인터페이스를 구현한 구현체 즉, 검증기를 WebDataBinder에 추가하면 컨트롤러에서 검증기를 자동으로 적용할 수 있게 된다. WebDataBinder는 Spring MVC에서 HTTP 요청 데이터를 특정 객체에 바인딩할 때 사용되는 도구로 데이터 바인딩뿐만 아니라 유효성 검사를 추가할 수 있는 기능이 포함되어 있다. 후에 설명할 @Validated 어노테이션이 바로 WebDataBinder에서 검증기(validator)를 찾아서 실행하는 역할을 한다.
BeanValidator
Bean Validator는 Java의 표준 유효성 검사 메커니즘이다. 이를 구현한 구현체가 바로 Hibernate Validator로 spring-boot-starter-validation 라이브러리를 사용하면 Spring Boot 애플리케이션에서 Hibernate Validator를 통해 Bean Validation을 자동으로 사용할 수 있다. BeanValidator는 바인딩에 실패한 필드에는 BeanValidation을 적용하지 않는다. 즉, 타입 변환에 성공하여 바인딩에 성공한 필드만 Validation 적용의 대상이 되는 것이다.
@Valid
@Valid는 JSR-303 표준 스펙(자바 표준)으로써 빈 검증기(Bean Validator)를 이용해 객체의 제약 조건을 검증하도록 지시하는 어노테이션이다. @Valid는 특정 객체의 유효성을 검증하는 데 주로 사용한다.
모든 요청은 디스패처 서블릿을 통해 컨트롤러로 전달된다. 전달 과정에서는 컨트롤러 메소드의 객체를 만들어주는 ArgumentResolver가 동작하는데, @Valid 역시 ArgumentResolver에 의해 처리가 된다. 대표적으로 @RequestBody는 Json 메세지를 객체로 변환해주는 작업이 ArgumentResolver의 구현체인RequestResponseBodyMethodProcessor가 처리하며, 이 내부에서 @Valid로 시작하는 어노테이션이 있을 경우에 유효성 검사를 진행한다. 검증에 오류가 있다면 MethodArgumentNotValidException 예외가 발생하게 되고, 디스패처 서블릿에 기본으로 등록된 예외 리졸버에 의해 400 BadRequest 에러가 발생한다.
이러한 이유로 @Valid는 기본적으로 컨트롤러에서만 동작하며 기본적으로 다른 계층에서는 검증이 되지 않는다. 다른 계층에서 파라미터를 검증하기 위해서는 @Validated와 결합되어야 하는데, 자세히 살펴보도록 하자.
@Validated
@Validated는 스프링 전용 검증 어노테이션으로 AOP 기반으로 메소드의 요청을 가로채서 유효성 검증을 진행한다.
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1")
@Validated
public class MenuController {
private final MenuCommandService menuCommandService;
@PostMapping("/{storeId}/menus/bulk")
public HankkiResponse<MenusPostResponse> createMenu(@PathVariable @Min(value = 1L) final long storeId,
@Valid @RequestBody final List<MenuPostRequest> request) {
List<MenuPostCommand> command = request.stream()
.map(r -> MenuPostCommand.of(r.name(), r.price()))
.toList();
return HankkiResponse.success(CommonSuccessCode.CREATED, menuCommandService.createMenus(MenusPostCommand.of(storeId, command)));
}
}
일반적으로 위의 코드처럼 클래스 레벨에 @Validated를 붙여주고, 유효성을 검증할 메소드의 파라미터에 @Valid를 붙여주어 2개를 조합하여 유효성 검증을 진행한다.
유효성 검증에 실패하게 되면, @Valid를 이용한 검증 실패시 발생했던 예외가 아닌, 다른 예외가 발생한다. 바로 ConstraintViolationException 예외가 발생하는데, 이는 ArgumentResolver에 의해 유효성 검증이 진행된 @Valid와 달리, @Validated는 AOP 기반으로 메소드 요청을 인터셉터하여 처리하기 때문이다. @Validated 를 클래스 레벨에 선언하면, 해당 클래스에 유효성 검증을 위한 AOP의 어드바이스 또는 인터셉터(MethodValidationInterceptor)가 등록된다. 그리고 해당 클래스의 메소드들이 호출될 때 AOP의 포인트 컷으로써 요청을 가로채서 유효성 검증을 진행한다.이러한 이유로 @Validated는 계층에 무관하게 스프링 빈이라면 유효성 검증을 진행할 수 있다. 대신 클래스에는 유효성 검증 AOP가 적용되도록 @Validated를, 검증을 진행할 메소드에는 @Valid를 선언해주어야 한다.
정리
'Spring' 카테고리의 다른 글
스프링 MVC 2편 - 섹션 10~11 (0) | 2025.03.17 |
---|---|
스프링 MVC 2편 - 섹션 7~9 (0) | 2025.03.17 |
스프링 MVC 1편 - 섹션 4~7 (0) | 2025.03.17 |
스프링 MVC 1편 - 섹션 1~3 (0) | 2025.03.17 |
스프링 핵심 원리 기본편 - 섹션 9~10 (0) | 2025.03.17 |