Programming/Spring Boot

@Valid @Validated 동작 원리 파헤치기

Jan92 2022. 7. 1. 01:12

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 내 메서드

동작 원리에서 가장 먼저 살펴봐야 할 부분인 '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 method

그중에서도 해당 클래스의 resolveArgument() 메서드 내부에서 유효성 검증이 진행되며, 문제가 있을 경우 MethodArgumentNotValidException 예외를 발생시키게 됩니다.

 

 

 

validateIfApplicable

유효성 검증이 일어나는 부분을 찾기 위해 validateIfApplicable() 메서드를 살펴보면, 유효성 검증을 위한 도구를 찾아오기 위해 추상 클래스인 ValidationAnnotationUtilsdetermineValidationHints() 메서드가 실행되는데요.

 

해당 부분을 통해 가져온 검증 도구를 가지고 최종적으로 validate() 메서드를 통해 검증을 진행하게 됩니다.

 

 

***

validatedIfApplicable() 메서드는 추상 클래스인 AbstractMessageConverterMethodArgumentResolver에 있으며, RequestResponseBodyMethodProcessor는 해당 추상 클래스를 상속받은 클래스입니다.

 

 

 

ValidationAnnotationUtils.determineValidationHint()

determineValidationHints() 메서드 부분이며, AnnotationUtils의 내부 메서드 getValue() 부분에 대한 내용은 이해하지 못했습니다

 

 

 

validate()

다시 validateIfApplicable() 메서드로 돌아와 내부적으로 최종 검증을 하는 validate() 메서드 부분입니다.

해당 메서드는 DataBinder Class에 구현되어 있습니다.

 

메서드 중간쯤 있는 getValidators() 메서드를 주목해보면, 이때 가지고 오는 Validator가 바로 위에서 ValidationAutoConfiguration Class를 통해 등록한 LocalValidatorFactoryBean에서 나오게 되는 것입니다.

 

 

결국 해당 요청에 대한 유효성 검사 정보는 resolveArgument() 메서드의 WebDataBinder에 최종적으로 담기게 되고, 분기문을 통해 에러가 있는 경우 예외를 발생시키게 되는 것입니다.

 

 


 

 

@Validated 동작 원리

앞에서 이야기한 것처럼 일반적으로 요청 객체에 대한 유효성 검증은 컨트롤러에서 최대한 처리하게 됩니다. 하지만 불가피하게 Service 단 같은 다른 곳에서 검증을 진행해야 하는 경우가 있을 수 있는데요.

ArgumentResolver에서 유효성 검사를 진행했던 @Valid과 달리 @ValidatedAOP(Aspect Oriented Programming)을 기반으로 메서드의 요청을 가로채서 유효성 검증을 진행합니다.

 

(AOP를 기반으로 동작한다는 것에 주목하며 내용 이어서 보겠습니다.)

 

 

methodValidationPostProcessor()

다시 ValidationAutoConfiguration 클래스로 돌아와서 methodValidationPostProcessor() 메서드를 살펴보겠습니다.

메서드 내용을 살펴보면 FilteredMethodValidationPostProcessor 객체가 빈으로 등록되는데요.

 

 

 

MethodValidationPostProcessor

FilteredMethodValidationPostProcessor 클래스는 MethodValidationPostProcessor를 상속한 클래스인데, 해당 클래스의 setValidatorFactory() 메서드를 보면 default ValidatorFactory의 dafault Validator를 사용한다는 것을 볼 수 있습니다.

 

이때 적용되는 Defualt ValidatorFactory가 ValidationAutoConfiguration 클래스에서 함께 빈으로 등록되었던 LocalValidatorFactoryBean가 되는 것입니다.

 

 

 

MethodValidationInterceptor

두 메서드 역시 MethodValidationPostProcessor 클래스의 메서드로 여기서 핵심은 createMethodValidationAdvice() 메서드를 통해 AOP AdviceMethodValidationInterceptor를 등록한다는 것입니다.

 

결국, 여기서 등록된 MethodValidationInterceptor가 메서드 요청을 가로채 유효성 검사를 진행하게 되는 것인데요.

 

 

 

invoke() method

MethodValidationInterceptor Classinvoke() 메서드입니다.

 

해당 메서드에서 유효성 검증이 진행되며, 이때 유효성 검사에 문제가 있을 경우 ConstraintViolationException 예외를 발생시키는데요. Controller 단에서 발생되는 MethodArgumentNotValidException 예외와 다르다는 것을 알아두어야 나중에 Exception 처리할 때 도움이 됩니다.

 

 

 

Service

(실제 AOP를 통한 유효성 검사를 사용할 때는 클래스단에 @Validated 어노테이션을 적용하고, 유효성을 검사할 객체에 @Valid 어노테이션을 적용해주면 됩니다.)

 

 

 

***

내용을 다시 정리하면서 봐도 헷갈리는 부분과 이해하지 못한 부분이 많은 것 같습니다. 해당 내용은 추후에 다시 한번 확인하며 수정이 필요할 것 같으며, 보시다가 잘못된 부분은 지적해주시면 확인하여 수정하겠습니다. 감사합니다. 

 

 

 

< 참고 자료 >

 

[SPRING] @Valid 어떻게 동작할까 - java bean validation

😐서론 애플리케이션 개발을 진행할 때 검증은 가장 중요한 작업이다. 클라이언트에서 데이터가 제대로 넘어왔는지, 비지니스 로직에서 인수가 제대로 된 값이 넘어왔는지, db에 값이 저장 혹

kdhyo98.tistory.com

 

 

[Spring] @Valid와 @Validated를 이용한 유효성 검증의 동작 원리 및 사용법 예시 - (1/2)

Spring으로 개발을 하다 보면 DTO 또는 객체를 검증해야 하는 경우가 있습니다. 이를 별도의 검증 클래스로 만들어 사용할 수 있지만 간단한 검증의 경우에는 JSR 표준을 이용해 간결하게 처리할 수

mangkyu.tistory.com

 

< 함께 보면 좋은 자료 >

 

(Spring Boot) Custom Validator 적용하는 방법, 단일 및 다중 필드

프로젝트에서는 클라이언트의 요청 값을 검증해야 하는 많은 경우가 있습니다. 이때 Java에서는 'Bean Validation'을 통해 유효성 검증을 실시하는데요. 'Bean Validation'은 빈 유효성 검사를 위한 Java API

wildeveloperetrain.tistory.com