신입 프로젝트를 진행하면서 유효성 검증을 위해서 제공되는 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를 받아오는 코드다.