execution 패턴 표현식
execution(public void set*(..))
: 반환형이 void이고, 메소드 이름의 set으로 시작하고 인자가 0개 이상인 메소드를 호출한다.executation(* dto.*.*())
: dto 패키지 탕비에 속한 인자가 없는 모든 메소드를 호출한다.executation(* dto..*.*(..))
: dto 패키지 및 하위 패키지에 있고 인자가 0개 이상인 메소드를 호출한다.execution(Long dto.StudentRepository.getAge(..))
: 반환형이 Long인 StudentRepository 타입의 getAge() 메소드를 호출한다.execution(* get*(*))
: 이름이 get으로 시작하고 인자가 1개인 메소드를 호출한다.execution(* get*(*, *))
: 이름이 get으로 시작하고 인자가 2개인 메소드를 호출한다.
Advice 적용 순서
- 하나의 pointcut에 여러 Advice를 적용할 수 있다.
// Aspect 지정 객체
@Aspect
public class CacheAspect {
private Map<Long, Object> cache = new HashMap<>();
@Pointcut("execution(public * chap07..*(long))")
public void cacheTarget() {
}
@Around("cacheTarget()")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
Long number = (Long) joinPoint.getArgs()[0];
if (cache.containsKey(number)) {
System.out.printf("CacheAspect: Get the item from Cache [%d]\n", number);
return cache.get(number);
}
Object result = joinPoint.proceed();
cache.put(number, result);
System.out.printf("CacheAspect: Put the item in Cache [%d]\n", number);
return result;
}
}
- 새로운 Aspect를 구현했다.
- bean 설정에서 두개의 Aspect를 아래와 같이 추가할 수 있다.
@Configuration
@EnableAspectJAutoProxy
public class AppContextWithCache {
@Bean
public CacheAspect cacheAspect() {
return new CacheAspect();
}
@Bean
public ExecutiveTimeAspect executiveTimeAspect() {
return new ExecutiveTimeAspect();
}
@Bean
public Calculator calculator() {
return new RecurrenceCalculator();
}
}
- 스프링컨테이너는 아래와 같이 생성한다
public class MainAspectWithCache {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppContextWithCache.class);
Calculator calculator = context.getBean("calculator", Calculator.class);
calculator.factorial(7);
calculator.factorial(7);
calculator.factorial(3);
calculator.factorial(7);
calculator.factorial(7);
calculator.factorial(3);
calculator.factorial(5);
calculator.factorial(5);
calculator.factorial(5);
calculator.factorial(5);
context.close();
}
}
- 실행 결과는 아래와 같다
RecurrenceCalculator.factorial([7]) runtime : 16534 ns
CacheAspect: Put the item in Cache [7]
CacheAspect: Get the item from Cache [7]
RecurrenceCalculator.factorial([3]) runtime : 6481 ns
CacheAspect: Put the item in Cache [3]
CacheAspect: Get the item from Cache [7]
CacheAspect: Get the item from Cache [7]
CacheAspect: Get the item from Cache [3]
RecurrenceCalculator.factorial([5]) runtime : 8021 ns
CacheAspect: Put the item in Cache [5]
CacheAspect: Get the item from Cache [5]
CacheAspect: Get the item from Cache [5]
CacheAspect: Get the item from Cache [5]
캐시에 객체가 존재하면 계산을 하지 않고 캐시를 가지고 온다.
- 이렇게 Aspect의 실행순서를 알 수 있다
- [CacheAspect] Proxy → [ExecutiveTimeAspect] Proxy → [RecurrenceCacluator] Instance
@Order
- 여러개의 Aspect를 지정한다고 항상 의도된대로 실행되는 것은 아니다.
- 순서 지정이 필요한 경우 Aspect가 지정된 클래스에 @Order 어노테이션을 지정하면된다.
@Aspect
@Order(2)
public class CacheAspect {
//..
@Aspect
@Order(1)
public class ExecutiveTimeAspect {
//..
- 실행 결과는 Cache를 뒤에 타도록 달라진다
CacheAspect: Put the item in Cache [7]
RecurrenceCalculator.factorial([7]) runtime : 201529 ns
CacheAspect: Get the item from Cache [7]
RecurrenceCalculator.factorial([7]) runtime : 276267 ns
CacheAspect: Put the item in Cache [3]
//... 중략
CacheAspect: Get the item from Cache [5]
RecurrenceCalculator.factorial([5]) runtime : 99159 ns
CacheAspect: Get the item from Cache [5]
RecurrenceCalculator.factorial([5]) runtime : 85201 ns
CacheAspect: Get the item from Cache [5]
RecurrenceCalculator.factorial([5]) runtime : 88659 ns
@Pointcut의 재사용
- CacheAspect와 ExecutiveTimeAspect를 구성하면서 @Pointcut 어노테이션의 필터를 사용해 대상을 지정하고있다.
- 둘다 같은 Joinpoint를 바라보고 있고 이것을 한쪽으로 합칠 수 있다.
- 아래에서는 CacheAspect에서 ExevuciteTimeAspect쪽의 Joinpoint를 바라보도록 만들어보겠다
// CacheAspect
// @Pointcut("execution(public * chap07..*(long))")
// public void cacheTarget() {
// }
@Around("ExecutiveTimeAspect.publicTarget()")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
//..
// ExecutiveTimeAspect
@Pointcut(value = "execution(public * chap07..*(..))")
public void publicTarget() {
}
//..
@Pointcut 어노테이션을 받는 메소드를 public으로 변경하면 다른 Aspect에서 Joinpoint를 바라볼 수 있다.
- @Pointcut을 외부로 빼내어 관리가 가능하다.
- 우선 joinpoint만을 위한 클래스를 만들어 @Pointcut 어노테이션과 필터를 적용한다.
// CommonPointcut
public class CommonPointcut {
@Pointcut(value = "execution(public * chap07..*(..))")
public void commonTarget() {
}
}
- 그다음 각 Aspect의 @Around 필드를 수정한다.
// CacheAspect
@Around("CommonPointcut.commonTarget()")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
//..
// ExecutiveTimeAspect
@Around("CommonPointcut.commonTarget()")
public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
//....
- 이렇게 하면 joinpoint를 효율적으로 관리할 수 있다.
여기까지 스프링의 핵심 기능인 스프링컨테이너, Bean객체와 설정, DI, 컴포넌트, AOP까지 알아봤다. 이후는 스프링 부트를 통한 실제 웹서비스를 만들기 위한 과정으로 넘어가겠다.