[Spring AOP] custom validaton 적용 이슈

신명철·2022년 10월 7일
0

errors

목록 보기
3/3

들어가며

신입 프로젝트를 진행하면서 유효성 검증을 위해서 제공되는 Util을 사용해야 하는 이유로@Valid를 사용하지 못하는 상황이었다. Util을 반복적으로 작성하는게 귀찮아서 AOP를 적용하려는 과정에서 AOP가 적용되지 않은 문제가 발생했다.

문제 상황

내가 의도했던 바는 다음과 같다.

  1. @ValidUtil 이라는 이름의 커스텀 어노테이션 생성
  2. Spring AOP를 통해 @ValidUtil이 달려 있는 파라미터를 대상으로 유효성 검증

1. @ValidUtil 생성

/* ValidUtil.annotaion */

@Target(ElementType.PARAMETR)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidUtil{
}

2. Aspect 생성

@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을 사용한 유효성 검증을 진행한다.

3. ValidUtil 사용

@RestController
@RequestMappin("/test")
public class Controller {

 	// 적용하고자 하는 파라미터에 어노테이션을 달아줬다.
	@PostMapping
	public void method(@ValidUtil @RequestBody Foo foo){
    }

}

2. 문제 발생

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) ~
  • 의도했던 대로 Util이 제공하는 Exception이 터지지 않았다.
  • ConstraintViolationException@Valid 의 유효성 검증을 통과하지 못했을 때 발생하는 예외다.
  • @Valid 어노테이션은 사용하지도 않았는데 왜 @Valid 을 사용한 것과 같은 결과가 나왔을까..

3. 문제 해결

결론부터 말하자면, @Valid는 "Valid"로 시작하는 모든 어노테이션을 대상으로 validation이 진행된다. 즉, 내가 만든 어노테이션인 @ValidUitl 뿐만 아니라, @ValidateUtil, @ValidXXXXXX도 검증의 대상이 된다.

문제 해결을 위해 @Valid가 동작하는 구현체를 파악할 필요가 있었고 가장 먼저 RequestResponseBodyMethodProcessorresolveArgument()를 시작으로 파악을 진행했다.

  • resolveArgument()에서는 유효성 검증이 진행되고, 문제가 있다면 MethodArgumentNotValidException이 발생한다.

  • 유효성 검증이 진행되는 validateIfApplicable() 메서드다. 유효성 검증을 위한 도구를 찾아오기 위해서 ValidationAnnotationUtils의 determineValidationHints() 가 실행된다.
  • determineValidationHints()를 통해 가져온 Validator의 validate() 를 통해 최종적으로 유효성을 검증하게 된다.

핵심 코드

  • 중간에 annotationType.getSimpleNAme().startsWith("Valid")를 보면 Valid로 시작하는 어노테이션을 대상으로 Validation이 진행된다는 것을 알 수 있다.

  • Validator를 통해 최종적으로 검증을 하는validate()메서드다.
  • 여기서 getValidators()는 dependency를 등록하면서 Spring boot 시작 시점에 자동적으로 등록된 Validator를 받아오는 코드다.

사진 출처

profile
내 머릿속 지우개

0개의 댓글