@Aspect에 대해 이해하기 위해서는 자동 프록시 생성기(AutoProxyCreator)와 어드바이저(Advisor)에 대한 이해가 필요하다.
해당 개념들이 생소한 경우 스프링이 지원하는 동적 프록시 기술, 동적 프록시를 위한 빈 후처리기를 참조하자.
스프링이 제공하는 AutoProxyCreator를 통해 프록시 기능을 적용하려면, Pointcut과 Advice로 구성된 Advisor를 구현하여 스프링 빈으로 등록하면 된다.
@Aspect는 매우 편리하게 Pointcut과 Advice로 구성된 Advisor를 생성하기 위한 애노테이션이다.
스프링은 @Aspect를 통해 프록시를 통한 AOP를 가능하게 한다.
이번 글에서는 우선 @Aspect를 통해 편리하게 Advisor를 생성하고, 해당 Advisor를 통해 프록시를 생성할 수 있다는 것에 초점을 두자.
예제 코드
LogTraceAspect는 @Aspect가 적용된 클래스이다.
@Aspect가 적용된 클래스에서 하나의 Pointcut과 하나의 Advice로 구성된 Advisor를 편리하게 정의할 수 있다.
execute()를 살펴보자. 해당 메소드에는 @Around라는 애노테이션이 적용되어있다.
@Around의 값은 Pointcut의 역할을 하며, @Around가 적용된 execute()의 내부 로직이 Advice의 역할을 한다.
execute()는 ProceedingJoinPoint joinPoint라는 파라미터를 가진다.
joinPoint 내부에는 프록시가 호출할 대상, 호출할 메서드, 전달 인자 등의 정보가 포함되어 있다.
ProceedingJoinPoint .getSignature().toShortString()을 통해 프록시의 호출 대상과 메소드의 정보를 추출할 수 있다.
그리고 ProceedingJoinPoint.proceed()를 통해 호출 대상의 메소드를 호출할 수 있다.
@Slf4j
@Aspect // @Aspect를 통해 어드바이저(어드바이스 1 + 포인트컷 1)을 편리하게 정의할 수 있다.
public class LogTraceAspect {
private final LogTrace logTrace;
public LogTraceAspect(LogTrace logTrace) {
this.logTrace = logTrace;
}
// @Around의 값은 포인트컷이며, @Around가 적용된 메소드가 어드바이스 역할을 한다.
@Around("execution(* hello.proxy.app..*(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
TraceStatus status = null;
try {
String message = joinPoint.getSignature().toShortString();
status = logTrace.begin(message);
// 호출 대상의 메소드를 호출
Object result = joinPoint.proceed();
logTrace.end(status);
return result;
} catch (Exception e) {
logTrace.exception(status, e);
throw e;
}
}
}
아래의 설정 파일 처럼 @Aspect가 적용된 객체를 빈으로 등록하면, AutoProxyCreator는 빈으로 등록된 @Aspect 객체를 찾아 Advisor로 변환하여 저장한다.
// 자동 프록시 생성기(AutoProxyCreator)는 빈으로 등록된 @Aspect 객체를 찾아 Advisor로 변환하여 저장한다.
@Configuration
@Import({AppV1Config.class, AppV2Config.class})
public class AopConfig {
@Bean
public LogTraceAspect logTraceAspect(LogTrace logTrace) {
return new LogTraceAspect(logTrace);
}
}
AutoProxyCreator의 2가지 기능
AutoProxyCreator는 2가지 기능을 제공한다.
하나는 빈으로 등록된 @Aspect 객체를 Advisor로 변환하여 저장하는 것이다.
그리고 다른 하나는 Advisor를 기반으로 프록시를 생성하는 것이다.
@Aspect 객체가 Advisor 객체로 변환되는 과정은 다음과 같다.
- 스프링 로딩 시점에 AutoProxyCreator가 호출된다.
- AutoProxyCreator는 빈으로 등록된 @Aspect 객체를 모두 조회한다.
- AspectJAdvisorsBuilder를 통해 @Aspect 기반 Advisor를 생성한다.
- 생성된 @Aspect 기반 Advisor를 AspectJAdvisorsBuilder 내부에 저장한다.
이제 AutoCreator가 Advisor를 기반으로 프록시를 생성하는 과정을 살펴보자.
@Aspect 기반 Advisor가 생성되고 저장되는 과정은 이미 진행된 상황이다.
- 빈으로 등록될 대상 객체를 생성한다(@Bean, @Component).
- 생성된 객체를 컨테이너의 빈 저장소에 등록하기 전에 AutoProxyCreator에게 전달한다.
- AutoProxyCreator는 빈 저장소에 등록된 Advisor를 모두 조회한다. 그리고 AsepctJAdvisorsBuilder 내부에 저장된 Advisor도 모두 조회한다.
- 조회한 Advisor들의 Pointcut을 통해 대상 객체가 프록시를 적용할 대상인지 아닌지를 판단한다. 이 때 대상 객체의 클래스 정보, 메서드 정보를 Advisor들의 Pointcut에 모두 매칭해본다. 조건이 하나라도 만족한다면 프록시 적용 대상이 된다.
- 프록시 적용 대상이란 ProxyFactory를 통해 프록시 객체를 생성하여 반환하고, 아니라면 대상 객체를 그대로 반환한다.
- 반환된 객체가 스프링 빈으로 등록된다.
AOP를 위한 프록시
지금까지 다음과 같은 많은 게시글들을 통해 프록시, 동적 프록시, 스프링이 지원하는 프록시 기술들을 알아보았다.
이렇게 프록시에 대해 자세하게 알아본 이유는 스프링의 가장 중요한 기능 중 하나인 스프링 AOP가 프록시를 통해 구현되기 때문이다.
다음 게시글부터는 스프링 AOP에 대해 알아보고자 한다.