AOP를 어느정도 공부하고 회사 업무에 써먹어 보기로 했다. 결제에 대한 로그 데이터를 DB에 Insert하는 로직을 AOP로 구현하고 코드 리뷰를 딱! 올렸다. AOP사용 시 주의 할 부분에 대한 리뷰가 달렸다. 아래의 리뷰와 같다.
메소드 명에 대한 와일드 카드 처리는 AOP를 설정한지 모르는 다른 개발자가 똑같은 prefix로 시작하는 경우 의도치 않은 Advice로직을 적용 시킬 수 있기 때문에 주의를 강조 했다.
이를 극복 할 수 있는 방법으로 Custom Annotation 클래스를 생성하여 메소드의 Annotation으로 pointcut을 잡아 보는 것을 알아보았다.
Custom Annotation
API에서 지정된 Annotation 이외에 개발자가 필요로 목적으로 정의한 Annotation을 말한다.
Annotation 생성하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29import java.lang.annotation.*;
//Annotation의 상속이 가능함
//JavaDoc 문서 추가 가능
// 컴파일 이후에도 JVM에 의해서 참조가 가능 (RetentionPolicy.RUNTIME)
//@Retention(RetentionPolicy.CLASS) // 컴파일러가 클래스를 참조할 때까지 유효
//@Retention(RetentionPolicy.SOURCE) // 어노테이션 정보는 컴파일 이후 사라짐 (ex. lombok)
({
ElementType.PACKAGE, // 패키지 선언시
ElementType.TYPE, // 타입 선언시
ElementType.CONSTRUCTOR, // 생성자 선언시
ElementType.FIELD, // 멤버 변수 선언시
ElementType.METHOD, // 메소드 선언시
ElementType.ANNOTATION_TYPE, // 어노테이션 타입 선언시
ElementType.LOCAL_VARIABLE, // 지역 변수 선언시
ElementType.PARAMETER, // 매개 변수 선언시
ElementType.TYPE_PARAMETER, // 매개 변수 타입 선언시
ElementType.TYPE_USE // 타입 사용시
})
public CustomAnnotation { //interface에 @를 붙여주면 Annotation으로 등록
/* enum 타입을 선언할 수 있습니다. */
public enum Quality {BAD, GOOD, VERYGOOD}
/* String은 기본 자료형은 아니지만 사용 가능합니다. */
String value();
/* 배열 형태로도 사용할 수 있습니다. */
int[] values();
/* enum 형태를 사용하는 방법입니다. */
Quality quality() default Quality.GOOD;
}
Annotation Pointcut 지정하기
예시로 Annotation을 추가하여 Pointcut을 설정해 보았다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package com.example.aopexam;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
//AOP Advisor를 Component로 선언하여 ComponentScan이 되어야 AOP적용 가능
"com.example.aopexam", "com.example.custom.annotation"}) (value={
public class AopExamApplication {
public static void main(String[] args) {
SpringApplication.run(AopExamApplication.class, args);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35package com.example.aopexam;
import com.example.custom.annotation.CustomLog;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
"/api/aop-test") (
public class ApiController {
private ApiService apiService;
//get 메소드에 @CustomLog추가
public void getTest() {
apiService.method(ApiType.GET);
}
public void postTest() {
apiService.method(ApiType.POST);
}
public void putTest() {
apiService.method(ApiType.PUT);
}
public void deleteTest() {
apiService.method(ApiType.DELETE);
}
}
1
2
3
4
5
6
7
8
9
10
11package com.example.custom.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
(RetentionPolicy.RUNTIME)
//메소드 어노테이션 적용 (ElementType.METHOD)
public CustomLog {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package com.example.custom.annotation;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
true) //cglib 사용 -> class기반 proxy생성 (proxyTargetClass =
public class CustomLogAdvisor {
//Annotation을 기준으로 pointcut설정
"@annotation(com.example.custom.annotation.CustomLog)") (
public void customLogPointcut() {
}
"customLogPointcut()") (value=
public void pringLog() {
System.out.println("메소드 실행 전 무조건 이 로그를 보게 될 것이야...");
}
}
1
2
3
4http://localhost:8080/api/aop-test
메소드 실행 전 무조건 이 로그를 보게 될 것이야...
[ApiService.method] apiType : GET