@Aspect AOP
- 김영한님의 스프링 원리 - 고급편 강의를 바탕으로
@Aspect애노테이션을 활용한 편리한 프록시 적용 방법과 스프링 자동 프록시 생성기의 동작 원리, 그리고 횡단 관심사 개념을 정리함
@Aspect 프록시 - 적용
개요
- 지금까지는
Advisor(포인트컷 + 어드바이스)를 직접 빈으로 등록하는 방식을 사용했음 - 스프링은 관점 지향 프로그래밍(AOP)을 지원하기 위해
@Aspect애노테이션을 제공하며, 이를 사용하면 포인트컷과 어드바이스를 하나의 클래스에 선언적으로 편리하게 작성할 수 있음
참고
@Aspect는 원래 AspectJ 프로젝트에서 제공하는 애노테이션임- 스프링은 이 애노테이션을 차용해서 프록시 기반의 AOP를 자체적으로 지원하고 있음
@Aspect 구조 매핑

LogTraceAspect 예제 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Slf4j
@Aspect
public class LogTraceAspect {
private final LogTrace logTrace;
public LogTraceAspect(LogTrace logTrace) {
this.logTrace = logTrace;
}
// @Around 값 = 포인트컷 표현식 (AspectJ 표현식)
// @Around 메서드 = 어드바이스 로직 본문
@Around("execution(* hello.proxy.app..*(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
TraceStatus status = null;
/*
joinPoint.getTarget() → 실제 호출 대상
joinPoint.getArgs() → 전달 인자
joinPoint.getSignature() → 메서드 시그니처 정보
*/
try {
String message = joinPoint.getSignature().toShortString();
status = logTrace.begin(message);
Object result = joinPoint.proceed(); // 실제 target 메서드 호출
logTrace.end(status);
return result;
} catch (Exception e) {
logTrace.exception(status, e);
throw e;
}
}
}
구성 요소 비교
| 요소 | 역할 | 기존 방식과 대응 |
|---|---|---|
@Aspect |
이 클래스가 Advisor 역할임을 선언함 | - |
@Around("expression") |
포인트컷 표현식을 지정함 | AspectJExpressionPointcut |
@Around 메서드 본문 |
실제 동작할 어드바이스 로직 | MethodInterceptor.invoke() |
ProceedingJoinPoint |
실제 호출 대상과 인수 정보를 가지고 있음 | MethodInvocation invocation |
joinPoint.proceed() |
진짜 타겟의 메서드를 실행함 | invocation.proceed() |
AopConfig (설정 파일)
1
2
3
4
5
6
7
8
9
10
11
@Configuration
@Import({AppV1Config.class, AppV2Config.class})
public class AopConfig {
// @Aspect가 있어도 반드시 스프링 빈으로 등록해야 인식하고 동작함
// @Component를 클래스 위에 붙여 컴포넌트 스캔 방식을 사용해도 동일함
@Bean
public LogTraceAspect logTraceAspect(LogTrace logTrace) {
return new LogTraceAspect(logTrace);
}
}
이전 방식과 @Aspect 방식 비교
- 이전 방식 (
Advisor직접 등록)Pointcut(AspectJExpressionPointcut등) 객체를 직접 생성하고 표현식을 설정함Advice(MethodInterceptor등) 객체를 생성하여 부가 기능 로직을 작성함- 만들어진 포인트컷과 어드바이스를
DefaultPointcutAdvisor로 조립하여 스프링 빈으로 등록함 - 어드바이저 생성 코드가 흩어질 수 있어 관련 로직을 한눈에 파악하기 번거로움
1 2 3 4 5 6 7 8 9 10
@Configuration public class AopConfig { @Bean public Advisor advisor(LogTrace logTrace) { AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); pointcut.setExpression("execution(* hello.proxy.app..*(..))"); LogTraceAdvice advice = new LogTraceAdvice(logTrace); return new DefaultPointcutAdvisor(pointcut, advice); } }
@Aspect방식- 클래스에
@Aspect애노테이션을 적용하고 자동 프록시 생성기를 통해 어드바이저로 변환함 @Around("포인트컷 표현식")애노테이션을 메서드에 붙여 포인트컷을 유연하게 지정함- 해당 애노테이션이 붙은 메서드의 내부 로직이 어드바이스 역할을 수행함
- 포인트컷과 어드바이스를 하나의 클래스 안에 모아서 선언적으로 설계할 수 있어 관리가 편리함
1 2 3 4 5 6 7 8
@Aspect public class LogTraceAspect { @Around("execution(* hello.proxy.app..*(..))") public Object execute(ProceedingJoinPoint joinPoint) throws Throwable { // 부가 기능 로직 (Advice) return joinPoint.proceed(); } }
- 클래스에
@Aspect 프록시 - 동작 원리
자동 프록시 생성기(AnnotationAwareAspectJAutoProxyCreator)의 두 가지 역할

- 빈 후처리기인 자동 프록시 생성기(
AnnotationAwareAspectJAutoProxyCreator)는 이름 그대로@Aspect애노테이션을 인지하고 자동으로 처리하는 추가 기능을 가지고 있음
@Aspect → Advisor 변환 및 저장 과정

- 애플리케이션 로딩 시
- 스프링 애플리케이션이 구동될 때 자동 프록시 생성기가 호출됨
- 조회
- 컨테이너에서
@Aspect가 붙은 빈 객체들을 모두 찾아냄
- 컨테이너에서
- 생성 요청
- 알아낸
@Aspect클래스의 정보를 바탕으로 어드바이저 빌더에게Advisor생성을 위임함
- 알아낸
- 캐시 저장
- 생성된
Advisor객체들을 내부에 캐싱해 두고 빠르게 재사용함
- 생성된
@Aspect 어드바이저 빌더 (BeanFactoryAspectJAdvisorsBuilder)
- 파싱된 정보로 실제
Advisor인스턴스를 만들고 내부 캐시 저장소에 보관하는 핵심 역할을 수행함- 이미 생성된
Advisor에 대한 요청이 오면 새로 만들지 않고 캐싱된 객체를 반환하여 성능을 최적화함
Advisor 기반 프록시 생성 로직

- 객체 생성 및 전달
- 빈이 생성되면 스프링이 이를 자동 프록시 생성기에게 우선 넘김
- Advisor 조회
- 자동 프록시 생성기는 1번(직접 빈으로 등록한 Advisor)과 2번(빌더 캐시에 저장된 @Aspect 기반 Advisor) 목록을 모두 조회함
- 포인트컷 스캔
- 확보한 목록을 토대로 전달받은 빈 객체의 클래스와 모든 메서드를 확인하며 포인트컷 통과 여부를 검사함
- 프록시 생성 및 등록
- 조건에 통과한 내역이 하나라도 있다면 원본 객체 대신 프록시를 생성하고, 이 프록시 객체를 스프링 컨테이너에 빈으로 등록함
자동 프록시 생성기의 전체 흐름 요약

- 이처럼 스프링 초기 로딩 시점에 어드바이저 등록 작업을 1회 완료하고, 이후 각 빈이 생성될 때마다 매칭 여부를 검사하여 자동 프록시를 씌우게 됨
횡단 관심사
횡단 관심사 (Cross-cutting Concerns)란?

- 핵심 비즈니스 로직(주문, 결제 등)이 아닌 영역 단위(보안, 로그 추적 등)로 여러 모듈에 공통적으로 적용되는 부가 기능을 지칭함
최종 사용법
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Slf4j
@Aspect
@Component
public class LogTraceAspect {
private final LogTrace logTrace;
public LogTraceAspect(LogTrace logTrace) {
this.logTrace = logTrace;
}
@Around("execution(* hello.proxy.app..*(..)) && !execution(* hello.proxy.app..noLog(..))")
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와@Around만 사용하여 코드를 작성하면, 스프링이 다음과 같은 복잡한 과정을 내부적으로 모두 자동화해 줌@Aspect→Advisor변환AnnotationAwareAspectJAutoProxyCreator가 처리함
- 동적 프록시 기술(JDK, CGLIB) 선택
ProxyFactory가 상황에 맞게 최적의 방식을 자동 선택함
- 포인트컷 적용 대상 검증
- 자동 프록시 생성기가 빈 생성 단계를 가로채어 검증을 처리함
- 컴포넌트 스캔 자동 등록
- 일반 컴포넌트들도 스프링 빈 로딩 시점에 자동으로 프록시가 적용됨
- 한 대상에 중복 프록시 적용 방지
- 대상마다 생성된 단일 프록시 내에 어드바이저를 리스트로 담아 최적화함
연습 문제
-
스프링에서 @Aspect를 사용하여 AOP 프록시를 적용할 때 가장 큰 장점은 무엇일까요?
a. 복잡한 AOP 설정 간소화
- 복잡한 설정 없이 편리하게 어드바이저를 정의하고 적용할 수 있다는 점임
@Aspect는 포인트컷과 어드바이스 조합인 어드바이저 생성을 하나의 클래스 내에서 선언적으로 매우 간편하게 구성해 줌
-
스프링의 자동 프록시 생성기는 @Aspect 어노테이션이 붙은 빈에 대해 어떤 역할을 수행할까요?
a. 빈을 어드바이저로 변환
- 자동 프록시 생성기 빌더가
@Aspect빈을 찾아내어 그 내부의 정보(@Around등)를 어드바이저(포인트컷 + 어드바이스) 객체로 변환하여 캐시에 보관하는 역할을 수행함
- 자동 프록시 생성기 빌더가
-
스프링의 자동 프록시 생성기는 어떤 기준으로 특정 빈에 프록시를 적용할지 결정할까요?
a. 해당 빈에 어드바이저의 포인트컷 일치
- 등록되거나 캐시된 어드바이저 중 단 하나라도 해당 빈의 어떠한 메서드든 포인트컷 조건에 부합하여 들어맞는 결과가 나오면 즉시 프록시 적용 대상으로 간주함
-
애플리케이션의 여러 계층(컨트롤러, 서비스 등)에 걸쳐 반복적으로 나타나는 로깅과 같은 기능은 어떤 유형의 관심사로 분류될까요?
a. 횡단 관심사
- 핵심 기능 로직이 아니라 여러 모듈이나 계층에 횡단면처럼 걸쳐 공통적으로 반복 적용되는 부가 성격의 기능들을 횡단 관심사(Cross-Cutting Concern)라고 통칭함
-
프록시가 횡단 관심사를 다루는 데 유용하게 사용되는 주된 이유는 무엇일까요?
a. 핵심 로직 수정 없이 기능 추가/변경 용이
- 원본 도메인의 비즈니스 로직 코드를 단 한 줄도 변경하지 않고, 외부에서 프록시를 통해 침투적으로 횡단 관심사 기능(로깅, 트랜잭션 등)을 깔끔하게 주입하고 분리할 수 있기 때문임
- 원본 도메인의 비즈니스 로직 코드를 단 한 줄도 변경하지 않고, 외부에서 프록시를 통해 침투적으로 횡단 관심사 기능(로깅, 트랜잭션 등)을 깔끔하게 주입하고 분리할 수 있기 때문임
요약 정리
@Aspect애노테이션을 사용하면 하나의 클래스 안에 포인트컷과 어드바이스를 모아서 편리하게 어드바이저를 정의할 수 있음- 자동 프록시 생성기는
@Aspect를 찾아 어드바이저로 변환하고 캐시하며, 빈 생성 시점에 이를 활용하여 자동으로 프록시를 매핑하고 적용해 줌