신입 프로젝트를 진행하면서 유효성 검증을 위해서 제공되는 Util을 사용해야 하는 이유로@Valid를 사용하지 못하는 상황이었다. Util을 반복적으로 작성하는게 귀찮아서 AOP를 적용하려는 과정에서 AOP가 적용되지 않은 문제가 발생했다.
내가 의도했던 바는 다음과 같다.
@ValidUtil 이라는 이름의 커스텀 어노테이션 생성@ValidUtil이 달려 있는 파라미터를 대상으로 유효성 검증@ValidUtil 생성/* ValidUtil.annotaion */
@Target(ElementType.PARAMETR)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidUtil{
}
@Aspect
@Component
public class ValidUtilAspect{
@Pointcut("execution(~)")
public void controller(){};
@Before("controller()")
public void doValidate(Joinpoint joinPoint){
Method method = MethodSignature.class.cast(joinPoint.getSignature()).getMethod();
Object[] args = joinPoint.getArgs();
Annotaion[][] annotations = method.getParameterAnnotations();
for(int argIdx = 0; argIdx < args.length; argIdx++){
for(Annotation argAnnotation : annotations[argIdx]){
if(argAnnotation instaceof ValidUtil){
/*
* 제공되는 유틸을 이용한
* Validation 진행
*/
}
}
}
}
}
@ValidUtil이 달려있는 파라미터들을 대상으로 제공되는 Util을 사용한 유효성 검증을 진행한다.ValidUtil 사용@RestController
@RequestMappin("/test")
public class Controller {
// 적용하고자 하는 파라미터에 어노테이션을 달아줬다.
@PostMapping
public void method(@ValidUtil @RequestBody Foo foo){
}
}
javax.validation.ConstraintViolationException: Foo.foo: 널이어서는 안됩니다.
at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:120) ~[spring-context-5.3.14.jar:5.3.14]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.14.jar:5.3.14]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.14.jar:5.3.14]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698) ~[spring-aop-5.3.14.jar:5.3.14]
at com.mangkyu.employment.interview.app.quiz.controller.QuizController$$EnhancerBySpringCGLIB$$b23fe1de.getQuizList(<generated>) ~[main/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~
ConstraintViolationException 은 @Valid 의 유효성 검증을 통과하지 못했을 때 발생하는 예외다.@Valid 어노테이션은 사용하지도 않았는데 왜 @Valid 을 사용한 것과 같은 결과가 나왔을까..결론부터 말하자면, @Valid는 "Valid"로 시작하는 모든 어노테이션을 대상으로 validation이 진행된다. 즉, 내가 만든 어노테이션인 @ValidUitl 뿐만 아니라, @ValidateUtil, @ValidXXXXXX도 검증의 대상이 된다.
문제 해결을 위해 @Valid가 동작하는 구현체를 파악할 필요가 있었고 가장 먼저 RequestResponseBodyMethodProcessor의 resolveArgument()를 시작으로 파악을 진행했다.

resolveArgument()에서는 유효성 검증이 진행되고, 문제가 있다면 MethodArgumentNotValidException이 발생한다.
validateIfApplicable() 메서드다. 유효성 검증을 위한 도구를 찾아오기 위해서 ValidationAnnotationUtils의 determineValidationHints() 가 실행된다.determineValidationHints()를 통해 가져온 Validator의 validate() 를 통해 최종적으로 유효성을 검증하게 된다.
annotationType.getSimpleNAme().startsWith("Valid")를 보면 Valid로 시작하는 어노테이션을 대상으로 Validation이 진행된다는 것을 알 수 있다.
validate()메서드다.getValidators()는 dependency를 등록하면서 Spring boot 시작 시점에 자동적으로 등록된 Validator를 받아오는 코드다.