@Configuration
@EnableAspectJAutoProxy
public class AppCtxWithCache {
@Bean
public ExeTimeAspect exeTimeAspect() {
return new ExeTimeAspect();
}
@Bean
public CacheAspect cacheAspect() {
return new CacheAspect();
}
@Bean
public Calculator calculator() {
return new RecCalculator();
}
}
CacheAspect: Cache에 추가[7]
RecCalculator.factorial([7]) 실행시간 : 446500 ns
CacheAspect: Cache에서 구함[7]
RecCalculator.factorial([7]) 실행시간 : 118600 ns
CacheAspect: Cache에 추가[5]
RecCalculator.factorial([5]) 실행시간 : 99200 ns
CacheAspect: Cache에서 구함[5]
RecCalculator.factorial([5]) 실행시간 : 90300 ns
@Bean
public CacheAspect cacheAspect() {
return new CacheAspect();
}
@Bean
public ExeTimeAspect exeTimeAspect() {
return new ExeTimeAspect();
}
@Bean
public Calculator calculator() {
return new RecCalculator();
}
RecCalculator.factorial([7]) 실행시간 : 35800 ns
CacheAspect: Cache에 추가[7]
CacheAspect: Cache에서 구함[7]
RecCalculator.factorial([5]) 실행시간 : 12400 ns
CacheAspect: Cache에 추가[5]
CacheAspect: Cache에서 구함[5]
--> 순서에 따라, 실행이 달라짐
factorial(7) 실행은 같은데, 첫 번째는 ExeTimeAspect와 CacheAspect가 다 적용, 후자는 CacheAspect만 적용됨
* 이는, Advice를 "CacheAspect 프록시 - ExeTimeAspect 프록시 - 실제 대상객체" 순으로 적용했기 때문
* 14행에서 구한 calculator빈은 사실 CacheAspect프록시 객체. 그런데 CacheAspect프록시 객체의 대상 객체는
* ExeTimeASpect의 프록시 객체. 마지막으로 ExeTimeAspect프록시의 대상객체가 실제 대상객체(AppCtx의 순서에 따라 대상객체가 변경)
따라서, CacheAspect의 Object result = joinPoint.proceed();를 만나면,
그 대상객체인 ExeTimeAspect의 measure로 넘어간다. 여기서 또 Object result = joinPoint.preceed()를 만나면,
그 대상객체인 Calculator의 factorial로 넘어간다.
다음으로, ExeTimeAspect의 finally를 실행
다음으로, CacheAspect의 put부터 실행
만약 순서가 다르다면, exe-cache-cal순으로 넘어가므로, 실행-추가-구함이 아니라, 추가-실행-구함-실행 이 되는것
근데 이건 개멍청하다. 객체지향에서 순서에 따라 바뀐다니??
따라서, @Order을 통해 그 순서를 지정해줄 필요가 있다.
@Bean
@Order(1)
public CacheAspect cacheAspect() {
return new CacheAspect();
}
@Bean
@Order(2)
public ExeTimeAspect exeTimeAspect() {
return new ExeTimeAspect();
}
//cacheAspect_1
@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 num = (Long)joinPoint.getArgs()[0]; //첫번째 인자를 long타입으로 구함
if(cache.containsKey(num)) { //위에서 구한 키값이 cache에 존재하면, 키에 해당하는 값을 구해 리턴
System.out.printf("CacheAspect: Cache에서 구함[%d]\n", num);
return cache.get(num);
}
Object result = joinPoint.proceed(); //long num 키값이 cache에 부재하면, 프록시 대상 객체를 실행
cache.put(num, result); //프록시 대상 객체의 결과(result)를 cache에 추가
System.out.printf("CacheAspect: Cache에 추가[%d]\n", num);
return result; //프록시 대상 객체의 실행결과를 리턴
}
}
/*
@Around값은 cacheTarget. @Pointcut 설정은 첫 번쨰 인자가 long인 메서드를 대상으로 한다. 따라서 execute()메서드는
Calculator의 factorial(long)메서드에 적용된다.
새 Aspect를 구현했으니, 스프링 설정 클래스에서 두 개의 Aspect 추가가 가능. 두 Aspect에서 설정한 pointcut은 모두
Calculator타입의 factorial()메서드에 적용됨
*/
pointcut이 아닌, around 애노테이션에 execution명시자를 직접지정
public class ExeTimeAspect {
@Pointcut("execution(public * chap07..*(..))") //Aspect 애노테이션 적용클래스는 advice와 pointcut 제공
//pointcut은 공통기능 적용할 대상을 설정. chap07패키지와 그 하위 패키지의 public 메서드를 pointcut으로 설정
private void publicTarget() {
}
/*
* public void publicTarget()을 사용시, 다른 클래스에 위치한 @Around애노테이션에서 publicTarget()메서드의
* Pointcut을 사용할 수 있다.
* 그리고 해당 Pointcut의 완전한 클래스 이름을 포함한 매서드 이름을 @Around 애노테이션에 사용하면 된다.
* 예) @Aspect
* public class CacheAspect {
* @Around("aspect.ExeTimeAspect.publicTarget()"0
* public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
* ...}
* }
단, CacheAspect와 ExeTimeAspect는 같은 패키지 내 위치하므로, 패키지 이름(aspect)는 굳이 빼도 됨.
여러 Aspect 내에서 공통으로 쓰는 Pointcut이 있다면, 별도 클래스에 Pointcut을 정의하고, 각 Aspect 클래스에서 해당 Pointcut을 쓰도록 하면 관리가 용이.
public class CommonPointcut {
@Pointcut("execution(public * chap07..*(..))")
public void commonTarget() {
}
}
///
@Aspect
public class CacheAspect {
private Map<Long, Object> cache = new HashMap<>();
@Around("CommonPointcut.commonTarget()")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
Long num = (Long)joinPoint.getArgs()[0];
......
///
@Aspect
public class ExeTimeAspect {
@Around("CommonPointcut.commonTarget()")
public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
}
...
'동방프로젝트' 카테고리의 다른 글
spring의 transactional&proxy (2) | 2021.01.25 |
---|---|
8과 queryForObject() (2) | 2021.01.23 |
7과 (2) | 2021.01.20 |
@Qualifier 어노테이션 (2) | 2021.01.15 |
스프링 chap02 (2) | 2021.01.13 |