AOP를 통해 Custom Annotation 처리하기

 

들어가며

 

개발시 프레임워크를 사용하다보면 제공되는 수 많은 Annotation들이 자주보일 것이다.

EX) @Cacheable, @Controller, @Data 등

제공 되고있는 Annotation이 아닌 필요한 Annotation을 직접 커스텀 하여 선언, 사용할 수 있다.

 

Annotation 작성과 선언된 Annotation을 처리하는 AOP에 대한 간단한 내용을 포스팅하고자 한다.

(Annotation을 처리하는 방법은 AOP외에도 여러방법이 존재한다.)

 

 

Custom Annotation

 

@Inherited
@Retention(value = RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Custom {
    String firstValue();
    String secondValue();
}

 

Annotation 정의시 @interface 키워드를 Annotation 이름 앞에 붙여 작성한다.

firstValue, secondValue는 @Custom(firstValue = "", secondValue = "")와 같이 Annotation 선언시 셋팅해줄 값을 정의한다.

이외에 설명은 아래와 같다.

 

Annotation명 내용 속성
@Inherited 부모 클래스에 해당 Annotation이 선언됐다면 자식클래스에게도 상속시킨다.  
@Retention

해당 Annotation의 정보 유지 범위를 지정한다.

 

RetentionPolicy.SOURCE

// Annotations are to be discarded by the compiler.

 

RetentionPolicy.CLASS (default)

// Annotations are to be recorded in the class file by the compiler

but need not be retained by the VM at run time.  This is the default

behavior.

 

RetentionPolicy.RUNTIME

// Annotations are to be recorded in the class file by the compiler and

retained by the VM at run time, so they may be read reflectively.

@Target 해당 Annotation을 사용가능한 대상을 지정한다.

 

ElementType.TYPE

// Class, interface (including annotation type), or enum declaration

 

ElementType.FIELD 

// Field declaration (includes enum constants)

 

ElementType.METHOD

// Method declaration

 

ElementType.PARAMETER

// Formal parameter declaration

 

ElementType.CONSTRUCTOR

// Constructor declaration

 

ElementType.LOCAL_VARIABLE

// Local variable declaration

 

ElementType.ANNOTATION_TYPE

// Annotation type declaration

 

ElementType.PACKAGE

// Package declaration

 

ElementType.TYPE_PARAMETER

// Type parameter declaration (since 1.8)

 

ElementType.TYPE_USE

// Use of a type (since 1.8)

 

 

 

이제 정의한 @Custom Annotation을 메서드에 선언하면 아래와 같다. 

 

@Slf4j
@Service
public class AnnotationTestService {

    @Custom(firstValue = "firstValue", secondValue = "secondValue")
    public String getSomeValue() {
        log.info("getSomeValue() is called!");
    }
}
        

 

 

 

해당 메서드가 호출되는 시점에 메서드에 선언된 @Custom Annotation에 의해 처리되어야할 내용이 있다고 가정하자.

처리되는 내용은 해당 메서드 호출 전 세팅된 fisrtValue와 SecondValue 값을 로깅하는 것이다.

 

 

 

AOP를 통해 Custom Annotation 처리

 

AOP로 처리를 하기 이전에 AOP에 대해 간단하게 살펴보자.

 

AOP(Aspect Oriented Programming)는 횡단(공통) 관심사를 분리하여 처리하는 기술을 말한다. 예를 들면 비즈니스 로직에서 핵심 로직이 아닐 수 있는 공통적인 로깅, 보안 등과 같은 내용을 AOP를 통해 분리시켜 비즈니스 로직은 핵심 로직에만 집중 시킬 수 있다.

 

아래는 AOP에서 사용되는 용어들의 목록이다. 

 

타겟 (Target)
어드바이스를 주입할 대상을 말한다.

 

어드바이스(Advice)
횡단(공통) 관심사만을 분리하여 담은 내용을 말한다.

 

조인포인트 (JoinPoint)
어드바이스가 적용될 수 있는 메서드 또는 그 위치를 말한다.


포인트컷 (PointCut)

어드바이스(횡단(공통) 기능)를 주입시킬 대상(타켓)을 선정하기위한 방법을 말한다.

 

아래는 @Custom Annotation을 AOP를 통해 처리한 코드이다.

 

@Aspect
@Order(1) //JoinPoint에 여러 Advice가 걸려있을시 Advice 수행 순서를 정함.
@Slf4j
@Component
public class CustomAdvisor {

    @Around("@annotation(com.hwannny.technique.annotation.Custom)")
    public Object processCustomAnnotation(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
        Custom custom = methodSignature.getMethod().getAnnotation(Custom.class);
        log.info("execute custom annotation processing with annotation param = {}, {}", custom.firstValue(), custom.secondValue());
        log.info("Before invoke getSomeValue()");
        Object proceedReturnValue = proceedingJoinPoint.proceed();
        log.info("After invoke getSomeValue()");
        return proceedReturnValue;
    }
}

 

타켓 메서드의 Signature를 통해 @Custom Annotation에 셋팅된 값을 로깅해보는 간단한 내용이다.

proceedingJoinPoint.proceed()를 호출하면 타켓메서드가 수행되며 타겟메서드의 반환 값이 proceed()의 반환값으로 그대로 전달된다.

 

위의 코드로 살펴볼 추가적인 AOP 내용에 대해 알아보자.

 


+ AOP 내용

 

@Around이 위치한곳은 어드바이스를 정의한다.

 

아래는 어드바이스 종류이다.

 

@Before

- 어드바이스가 주입될 타켓이 호출되기전 어드바이스 내용이 주입, 수행된다.

 

@After

- 타켓이 수행한 내용 결과에 관계없이 수행이 끝나면 어드바이스 내용이 주입, 수행된다.

 

@AfterReturning

- 타켓에 대한 내용이 예외없이 성공적으로 수행되면 결과값을 반환 후 어드바이스 내용이 주입, 수행된다.

 

@AfterThrowing

- 타켓에 대한 내용이 예외를 던지게되면 어드바이스 내용이 주입, 수행된다.

 

@Around

- 타켓에 대한 내용 수행 전, 후 를 감싸 어드바이스 내용이 주입, 수행된다.

 

@annotation(com.hwannny.technique.annotation.Custom)와 같이 어드바이스 인수로 작성된 내용을 포인트컷 표현식이라고 하며

@annotation은 지정자, com.hwannny.technique.annotation.Custom은 타켓명세라고 한다.

 

아래는 지정자 종류이다.

 

@annotation()

- 특정 Annotation이 지정된 모든 메서드를 타켓으로 지정한다.

 

args() 

- args() 인수의 개수와 타입이 일치하는 모든 메서드를 타켓으로 지정한다.

- ex) args(java.lang.Integer) // 하나의 매개변수를 가지며 그 매개변수 타입이 java.lang.Integer인 모든 메서드

 

@args()

- @args() 인수의 개수가 일치하며 @args의 인수의 타입(Annotation)을 가지는 모든 메서드를 타켓으로 지정한다.

- ex) args(com.hwannny.technique.annotation.Custom) // 하나의 매개변수를 가지며 그 매개변수 타입이 @Custom을 가지는 모든 메서드

 

execution()

- 메서드 명세에 대해 여러 방법으로 조합이 가능한 지정자이다.

출처: 오라클 자바 커뮤니티

- 포스팅하기엔 케이스가 많아 출처자료 외에 구글링을 통해 알아보는것을 추천한다.

 

within()

- execution 지정자에서 클래스, 인터페이스까지만 적용된 케이스로 보면된다.

 

@within()

- @args()와 within()을 합친거라고 보면 쉬울듯 하다.

 

bean()

- ex) bean(example*) // 이름이 example로 시작되는 모든 Bean의 메서드를 타켓으로 지정한다.

 

target()

- ex) target(java.util.List) : 타겟 오브젝트가 java.util.List를 구현했다면 모든 메서드를 타켓으로 지정한다.

 

@target()

- ex) @target(com.hwannny.technique.annotation.Custom) // 어떤 오브젝트가 @Custom Annotation을 가진다면 해당 오브젝트의 모든 메서드를 타켓으로 지정한다.

 

 

 

실행 결과

 

 

getSomeValue() 메서드 내용이 수행되기전 @Custom선언시 세팅된 값으로 로깅되는것을 확인 할 수 있다. 

 

 

 

결론

 

만약 AOP만 사용되어 횡단 관심사가 분리되었다면 횡단 관심이 주입될 새로운 메서드에 대해서는 이미 작성된 aspect 타켓명세가 변경 될 가능성이 높다. 하지만 Annotation을 같이 사용하여 처리한다면 새로운 메서드에 대해 타켓명세를 변경할 필요가 없이 메서드에 Annotation만 선언하면 되기 때문에 변경성을 낮추고 확장성을 높여주는 처리 방법인것 같다.