(Aspect Oriented Programming) : 관점 지향 프로그래밍
관점 : 개발자의 공통적인 관심사항
공통 기능, 핵심 기능 분리 기술
spring-aop API
aspectjweaver 구현체
의존성 추가
implementation 'org.aspectj:aspectjweaver:1.9.22.1'
데코레이터 패턴
동적 프록시
CalculatorHandler
public class CalculatorHandler implements InvocationHandler {
private Object obj;
public CalculatorHandler(Object obj) {
this.obj = obj;
}
/**
* @param proxy the proxy instance that the method was invoked on
* @param method : 호출한 메서드의 정보
* 동적 메서드 호출 method.invoke(...)
*
* @param args : 메서드 호출시 넘겨준 값(인수)
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(proxy.getClass());
long stime = System.nanoTime(); //공통 기능
try {
Object result = method.invoke(obj, args); //핵심 기능 proxy 가 대신 수행
return result;
}finally {
long etime = System.nanoTime(); //공통 기능
System.out.printf("걸린시간 : %d%n", etime - stime);
}
}
}
void test1(){
Object obj = Proxy.newProxyInstance(
Calculator.class.getClassLoader(),
new Class[]{Calculator.class},
new CalculatorHandler(new RecCalculator())
);
Calculator cal = (Calculator)obj;
long reulst = cal.factorial(10L);
System.out.println(reulst);
System.out.println(cal);
}

.* : exam01.* 👉 exam01 패키지의 하위 클래스
예) exam01.RecCalculator
..* : exam01..* 👉 exam01 패키지를 포함한 하위 패키지 포함 모든 클래스
예) exam01.sub.RecCalculator, exam01.sub1.sub2.RecCalculator
* aopex.*.*() : 모든 반환값 == * aopex.*()
aopex.* 👉 aopex 패키지 모든 하위 클래스
aopex.*.* 👉 aopex 패키지 모든 하위 클래스의 모든 메서드
aopex.*.*() 👉 aopex 패키지 모든 하위 클래스의 매개변수가 없는 모든 메서드
* aopex..*.*(..) 👉 모든 반환값에 aopex패키지를 포함한 하위 패키지 포함 모든 클래스 모든 메서드 == * aopex..*(..)
* get*(*) 👉 get으로 시작하는 매개변수가 1개짜리 메서드
* get*(*,*) 👉 get으로 시작하는 매개변수가 2개짜리 메서드
* read*(Integer, ..) 👉 메서드 명이 read 시작, 첫번째 매개변수는 Integer고정, 두번째 부터는 0개이상 매개변수
@Before : 메서드가 호출되기 전 공통 기능
@After : 메서드가 호출된 후 공통 기능
@AfterReturing : 반환값을 내보낸 후 공통 기능
@AfterThrowing : 예외가 발생한 후 공통 기능
@Around : 메서드 호출 전, 호출 후 공통 기능
Object proceed() : 핵심 기능 대신 실행
Signature getSignature() : 호출한 메서드 정보
getTarget() : 실제 메서드를 호출한 객체(RecCalculator..)Object[] getArgs() : 인수목록시그니처가 다르면 다른 메서드(오버로드)
String getName()
String toLongString()
String toShortString()
프록시에서 캐시를 적용하기 위해서 ProxyCache가 먼저 동작하고 ProxyCalculator가 수행되어야한다. 이를 해결하기 위해 Order을 통해 순서를 보장해야한다.
ProxyCache
@Aspect
@Order(1) //순서보장
public class ProxyCache {
private Map<Long, Object> data = new HashMap<>();
@Pointcut("execution(* exam01..*(..))")
public void publicTarget(){
}
@Around("publicTarget()")
public Object process(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
Long num = (Long)args[0]; //첫번째 인자값
if(data.containsKey(num)){// 이미 캐시에 저장된 결과 값이 있으면
System.out.println("캐시사용");
return data.get(num);
}
Object result = joinPoint.proceed();
data.put(num, result); //캐시에 저장
System.out.println("캐시에 저장");
return result;
}
}
ProxyCalculator2
@Aspect
@Order(2) //순서보장
public class ProxyCalculator2 {
@Pointcut("execution(* exam01..*(..))")
public void publicTarget(){}
@Around("publicTarget()")
public Object process(ProceedingJoinPoint joinPoint) throws Throwable {
long stime = System.nanoTime();
try {
Object result = joinPoint.proceed();
return result;
}finally {
long etime = System.nanoTime();
System.out.printf("걸린시간 : %d%n", etime - stime);
}
}
}
AppCtx
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) //AOP 자동 설정 애노테이션, 인터페이스 기반 -> 서브클래스 기반 프록시 변경
public class AppCtx {
@Bean
public ProxyCache proxyCache(){
return new ProxyCache();
}
}

프록시의 기본설정은 인터페이스 기반이다. proxyTargetClass = true은 서브클래스 기반 프록시로 변경해준다.
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) //AOP 자동 설정 애노테이션, 인터페이스 기반 -> 서브클래스 기반 프록시 변경
public class AppCtx {
@Bean
public ProxyCalculator2 proxyCalculator2(){
return new ProxyCalculator2();
}
@Bean
public Calculator calculator() {
return new RecCalculator();
}
}
AppCtx
@Configuration
@EnableAspectJAutoProxy //AOP 자동 설정 애노테이션
public class AppCtx {
@Bean
public ProxyCalculator proxyCalculator(){
return new ProxyCalculator();
}
@Bean
public Calculator calculator() {
return new RecCalculator();
}
}