여러 메소드에 동일한 코드가 반복될 때 AOP를 적용할 수 있다. 예를 들어 로깅을 메소드의 호출 전/후에 간단하게 추가할 수 있다.
AOP의 기본 개념
1. Aspect (관점)
여러 클래스나 기능에 걸쳐서 있는 관심사 (e.g. @Transactional, @Cacheable)
2. Advice (조언)
AOP에서 실제로 적용하는 기능(로깅, 트랜잭션, 캐시, 인증 등)
3. Join point (연결 포인트)
특정 기능이 실행될 수 있는 연결 포인트
4. Pointcut (포인트 선택 방법)
Join point 중에서 해당 Aspect를 적용(수행)할 대상을 뽑을 조건식
5. Target Object
Advice가 적용될 대상 객체
6. AOP Proxy
대상 객체에 Aspect를 적용하는 경우 Advice를 덧붙이기 위해 하는 작업을 AOP Proxy라고 한다. 주로 CGLIB(Code Generation Library, 실행 중에 실시간으로 코드를 생성하는 라이브러리) 프록시를 사용하여 프록싱 처리를 한다.
7. Weaving
Advice를 비지니스 로직 코드에 삽입하는 것
AspectJ
기본적으로 제공되는 Spring AOP로는 Pointcut 등을 사용할 수 없다.
Aspect 생성
@Aspect
@Component
public class AopAspect {
}
Pointcut 선언
@Aspect
@Component
public class AopAspect {
@Pointcut("execution(*transfer(..))")
private void anyOldTransfer() {}
}
- 해당 Aspect의 Advice가 적용될 Join Point를 찾기위한 조건 생성
Pointcut 결합
@Aspect
@Component
public class AopAspect {
//public 메소드
@Pointcut("execution(public * * (..))")
private void anyPublicOperation() {}
//특정 패키지
@Pointcut("within(com.example.app..*)")
private void inApp() {}
//두 조건을 결합
@Pointcut("anyPublicOperation() && inApp()")
private void appOperation() {}
}
Advice
Pointcut의 전/후 등에 실행될 기능을 정의
Before Advice
미리 정의된 dataAccessOperation() 포인트 컷의 전에 doAccessCheck를 실행
@Aspect
public class BeforeExample {
@Before("com.example.app.CommonPointcuts.dataAccessOperation()")
public void doAccessCheck() {
}
}
After Returning Advice
미리 정의된 dataAccessOperation() 포인트 컷에서 return이 발생된 후 실행
@Aspect
public class AfterReturningExample {
@AfterReturning("com.example.app.CommonPointcuts.dataAccessOperation()")
public void doAccessCheck() {
}
}
Around Advice
businessService()라는 포인트 컷의 전/후에 필요한 동작을 추가
@Aspect
public class AroundExample {
@Around("com.example.app.CommonPointcuts.businessService()")
public void doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
//start stopwatch
Object retVal = pjp.proceed();
//stop stopwatch
return retVal;
}
}
AOP를 적용한 실행 시간 로그 출력
실제로 AOP를 적용하여 특정 메소드의 실행 시간을 로그로 출력하는 Aspect를 추가해보자.
1. annotation 추가
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TimeTrace {
}
2. Aspect 추가
@Slf4j
@Component
@Aspect
public class TimeTraceAspect {
// [Pointcut 선언] @TimeTrace 어노테이셔을 가지는 메소드
@Pointcut("@annotation(joo.project.my3d.aop.TimeTrace)")
private void timeTracePointcut() {
}
// [Advice 정의] timeTracePointcut()라는 포인트 컷의 전/후에 실행
@Around("timeTracePointcut()")
public Object traceTime(ProceedingJoinPoint pjp) throws Throwable {
StopWatch stopWatch = new StopWatch();
try {
stopWatch.start();
return pjp.proceed();
} finally {
stopWatch.stop();
log.debug("{} - Total time = {}s = {}ms",
joinPoint.getSignature().toShortString(),
stopWatch.getTotalTimeSeconds(),
stopWatch.getTotalTimeMillis());
}
}
}