Custom Validator, Message Bundle 등의 유효성 검사와 관련된 작업을 하면서 @Valid와 @Validated 어노테이션의 동작 원리에 대해 궁금해져서 해당 부분을 정리한 내용입니다.
(코드를 살펴봤지만 아직까지 세부적으로 이해하지 못한 부분이 많습니다. 설명이 부족할 수 있다는 점 미리 양해 부탁드리겠습니다.)
@Valid @Validated 차이점,
Spring에서는 유효성 검증을 위해 자바 표준 스펙인 JSR-303의 @Valid Annotation을 많이 사용합니다. 하지만 어떤 경우 @Validated Annotation도 사용되는 것을 볼 수 있는데요. 두 어노테이션의 차이점은 무엇일까요?
@Validated 어노테이션은 JSR-303의 업그레이드 버전으로 @Valid 어노테이션이 javax.validation 패키지에 속하는 반면, @Validated 어노테이션은 org.springframework.validation.annotation에 속합니다.
즉, 스프링 프레임워크에서 제공되는 기능인데요. @Validated는 @Valid의 기능을 포함하고 있으며, 추가적으로 유효성을 검증할 옵션에 대한 그룹을 지정할 수 있는 기능이 있다는 차이점이 있습니다.
가장 먼저 살펴볼 ValidationAutoConfiguration
동작 원리에서 가장 먼저 살펴봐야 할 부분인 'ValidationAutoConfiguration Class'입니다.
스프링 프레임워크에서는 해당 클래스를 통해서 'LocalValidatorFactoryBean'과 'MethodValidationPostProcessor'를 Bean으로 등록시키는데요.
즉, 스프링 부트에서는 spring-boot-starter-validation 의존성만 추가하면 검증을 위한 기본적인 빈들이 자동으로 등록됩니다.
(@SpringBootApplication 어노테이션이 @EnableAutoConfiguration 어노테이션을 포함하고 있습니다.)
먼저 defaultValidator() 메서드는 메서드명 그대로 default validator를 등록하기 위한 것으로, 해당 메서드가 동작하며 LocalValidatorFactoryBean을 DefaultValidatorFactory로 등록해주기 때문에 유효성 검사 부분에서 따로 validator를 등록하지 않아도 사용할 수 있게 됩니다.
methodValidationPostProcessor() 메서드는 @Validated 어노테이션을 이용한 유효성 검증에 사용되는 부분입니다.
자세한 내용은 아래에서 살펴볼 것이지만 간단하게 짚고 넘어가면, @Valid 어노테이션은 Controller 단에서만 유효성 검사가 가능한데요.
일반적으로 파라미터에 대한 유효성 검증은 Controller 단에서 최대한 처리하는 것이 좋지만, 경우에 따라 다른 곳에서도 파라미터에 대한 검증이 필요합니다.
이런 경우에 AOP 기반으로 메서드의 요청을 가로채서 유효성 검증을 진행할 수 있는 것이 바로 @Validated 어노테이션이며, 해당 메서드를 통해 Bean 등록된 MethodValidationPostProcessor이 해당 역할을 하는 부분입니다.
*** @ConditionalOnMissingBean
해당 어노테이션의 경우 스프링 부트 프로젝트에서 같은 빈이 정의되어 있을 때에는 쓰지 않고, 만약 없으면 해당 빈을 등록하여 쓰게 하는 기능입니다.
@ConditionalOnMissingBean(Validator.class)의 경우 Validator 클래스가 빈으로 등록되지 않았을 경우 해당 메서드가 실행되며 빈으로 등록되는 것입니다.
@Valid 동작 원리
외부에서 들어오는 모든 요청은 디스패처 서블릿(Dispatcher Servlet)을 통해 Controller로 전달됩니다.
이때 컨트롤러에서 JSON 형식의 데이터를 받는 @ResponseBody 어노테이션을 사용하는 경우 'HandlerMethodArgumentResolver Interface'의 구현체인 'RequestResponseBodyMethodProcessor Class'가 해당 요청을 처리하게 되는데요.
그중에서도 해당 클래스의 resolveArgument() 메서드 내부에서 유효성 검증이 진행되며, 문제가 있을 경우 MethodArgumentNotValidException 예외를 발생시키게 됩니다.
유효성 검증이 일어나는 부분을 찾기 위해 validateIfApplicable() 메서드를 살펴보면, 유효성 검증을 위한 도구를 찾아오기 위해 추상 클래스인 ValidationAnnotationUtils의 determineValidationHints() 메서드가 실행되는데요.
해당 부분을 통해 가져온 검증 도구를 가지고 최종적으로 validate() 메서드를 통해 검증을 진행하게 됩니다.
***
validatedIfApplicable() 메서드는 추상 클래스인 AbstractMessageConverterMethodArgumentResolver에 있으며, RequestResponseBodyMethodProcessor는 해당 추상 클래스를 상속받은 클래스입니다.
determineValidationHints() 메서드 부분이며, AnnotationUtils의 내부 메서드 getValue() 부분에 대한 내용은 이해하지 못했습니다
다시 validateIfApplicable() 메서드로 돌아와 내부적으로 최종 검증을 하는 validate() 메서드 부분입니다.
해당 메서드는 DataBinder Class에 구현되어 있습니다.
메서드 중간쯤 있는 getValidators() 메서드를 주목해보면, 이때 가지고 오는 Validator가 바로 위에서 ValidationAutoConfiguration Class를 통해 등록한 LocalValidatorFactoryBean에서 나오게 되는 것입니다.
결국 해당 요청에 대한 유효성 검사 정보는 resolveArgument() 메서드의 WebDataBinder에 최종적으로 담기게 되고, 분기문을 통해 에러가 있는 경우 예외를 발생시키게 되는 것입니다.
@Validated 동작 원리
앞에서 이야기한 것처럼 일반적으로 요청 객체에 대한 유효성 검증은 컨트롤러에서 최대한 처리하게 됩니다. 하지만 불가피하게 Service 단 같은 다른 곳에서 검증을 진행해야 하는 경우가 있을 수 있는데요.
ArgumentResolver에서 유효성 검사를 진행했던 @Valid과 달리 @Validated는 AOP(Aspect Oriented Programming)을 기반으로 메서드의 요청을 가로채서 유효성 검증을 진행합니다.
(AOP를 기반으로 동작한다는 것에 주목하며 내용 이어서 보겠습니다.)
다시 ValidationAutoConfiguration 클래스로 돌아와서 methodValidationPostProcessor() 메서드를 살펴보겠습니다.
메서드 내용을 살펴보면 FilteredMethodValidationPostProcessor 객체가 빈으로 등록되는데요.
FilteredMethodValidationPostProcessor 클래스는 MethodValidationPostProcessor를 상속한 클래스인데, 해당 클래스의 setValidatorFactory() 메서드를 보면 default ValidatorFactory의 dafault Validator를 사용한다는 것을 볼 수 있습니다.
이때 적용되는 Defualt ValidatorFactory가 ValidationAutoConfiguration 클래스에서 함께 빈으로 등록되었던 LocalValidatorFactoryBean가 되는 것입니다.
두 메서드 역시 MethodValidationPostProcessor 클래스의 메서드로 여기서 핵심은 createMethodValidationAdvice() 메서드를 통해 AOP Advice인 MethodValidationInterceptor를 등록한다는 것입니다.
결국, 여기서 등록된 MethodValidationInterceptor가 메서드 요청을 가로채 유효성 검사를 진행하게 되는 것인데요.
MethodValidationInterceptor Class의 invoke() 메서드입니다.
해당 메서드에서 유효성 검증이 진행되며, 이때 유효성 검사에 문제가 있을 경우 ConstraintViolationException 예외를 발생시키는데요. Controller 단에서 발생되는 MethodArgumentNotValidException 예외와 다르다는 것을 알아두어야 나중에 Exception 처리할 때 도움이 됩니다.
(실제 AOP를 통한 유효성 검사를 사용할 때는 클래스단에 @Validated 어노테이션을 적용하고, 유효성을 검사할 객체에 @Valid 어노테이션을 적용해주면 됩니다.)
***
내용을 다시 정리하면서 봐도 헷갈리는 부분과 이해하지 못한 부분이 많은 것 같습니다. 해당 내용은 추후에 다시 한번 확인하며 수정이 필요할 것 같으며, 보시다가 잘못된 부분은 지적해주시면 확인하여 수정하겠습니다. 감사합니다.
< 참고 자료 >
< 함께 보면 좋은 자료 >
'Programming > Spring Boot' 카테고리의 다른 글
세션을 사용한 스프링 시큐리티 구현(WebSecurityConfigurerAdapter deprecated) (0) | 2022.07.21 |
---|---|
스프링 시큐리티 SecurityContextHolder에 Authentication(인증) 정보가 저장되는 과정 (0) | 2022.07.13 |
(Spring Boot) Custom Validator 적용하는 방법, 단일 및 다중 필드 (3) | 2022.06.29 |
Spring swagger 3 사용방법(springdoc-openapi-ui) (2) | 2022.06.25 |
@RequestBody @ResponseBody 어노테이션 이해하고 사용하기 (2) | 2022.05.13 |