2025. 6. 9. 21:26ㆍSpring Framework/Aspect Oriented Programming with Spring
🌿 Spring AOP @EnableAspectJAutoProxy
@EnableAspectJAutoProxy
는 Spring AOP 환경에서 AspectJ 스타일의 애스펙트(@Aspect
)를 적용할 수 있게 해주는 애노테이션입니다. XML 설정 방식의 <aop:aspectj-autoproxy>
를 대체하는 자바 기반 설정 방식이라고 이해하면 됩니다.
✅ 왜 @EnableAspectJAutoProxy
가 필요한가?
Spring은 디폴트로 POJO 객체에 대해 AOP 기능을 적용하려면 프록시(proxy)를 생성해야 합니다.
그리고 어떤 클래스가 @Aspect
로 마킹되어 있을 때, 이를 처리해주는 AOP Post processor(Advisor, Advice)를 동작하게 만드는 설정이 바로 @EnableAspectJAutoProxy
입니다.
🧩 기본 사용법
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
@Bean
public FooService fooService() {
return new FooService();
}
@Bean
public MyAspect myAspect() {
return new MyAspect();
}
}
FooService
: AOP를 적용하고 싶은 일반적인 비즈니스 컴포넌트MyAspect
:@Aspect
로 선언된 공통 기능 모듈
public class FooService {
public void doSomething() { ... }
}
@Aspect
public class MyAspect {
@Before("execution(* FooService+.*(..))")
public void beforeAdvice() {
System.out.println(">>> Before FooService method");
}
}
이렇게 설정하면, FooService
의 메서드가 호출될 때마다 MyAspect
의 advice가 함께 실행됩니다.
🔁 JDK vs CGLIB: 프록시 전략 선택
Spring AOP는 내부적으로 두 가지 프록시 전략을 사용합니다.
1. 디폴트: JDK 동적 프록시 (proxyTargetClass = false
)
- 타겟 클래스가 인터페이스를 구현하고 있어야 적용 가능
- 프록시는 오직 인터페이스 타입만 구현함
- 타겟 클래스(
FooService
)를 상속하지 않음
@EnableAspectJAutoProxy // 기본값 proxyTargetClass = false
2. CGLIB 서브클래스 프록시 (proxyTargetClass = true
)
- 타겟 클래스가 인터페이스를 구현하지 않아도 AOP 적용 가능
- 프록시는 타깃 클래스를 상속(subclass)하여 생성됨
- 단,
final
클래스나final
메서드는 프록싱 불가
@EnableAspectJAutoProxy(proxyTargetClass = true)
🏷️ @ComponentScan과 함께 쓰기
@Aspect
애노테이션이 붙은 클래스를 명시적으로 @Bean
으로 등록하지 않고, @ComponentScan
을 통해 자동 감지할 수도 있습니다.
@Component
public class FooService { ... }
@Aspect
@Component
public class MyAspect { ... }
그리고 구성 클래스에서 컴포넌트 스캔만 지정하면 끝:
@Configuration
@ComponentScan("com.foo")
@EnableAspectJAutoProxy
public class AppConfig {
// @Bean 선언 없이 자동 등록
}
⚠️ 주의할 점
🧭 1. 로컬 ApplicationContext
에만 적용됨
@EnableAspectJAutoProxy
는 해당 구성 클래스가 적용된 ApplicationContext에만 적용됩니다.- 웹 애플리케이션에서
RootContext
와DispatcherServlet Context
를 분리해 사용하는 경우,
각각에 따로 선언해야 AOP가 적용됩니다.
📦 2. AspectJ Weaver 의존성 필요
@EnableAspectJAutoProxy
자체는 Spring AOP 기능이지만, 내부적으로 AspectJ의 지원을 사용합니다.- 따라서 classpath에 반드시 다음 의존성이 있어야 합니다:
// build.gradle 예시
implementation 'org.aspectj:aspectjweaver'
✅ 마무리 정리
항목 | 설명 |
---|---|
@EnableAspectJAutoProxy |
@Aspect 클래스 활성화 |
proxyTargetClass=false |
JDK 동적 프록시 사용 (인터페이스 필수) |
proxyTargetClass=true |
CGLIB 기반 서브클래스 프록시 사용 |
@ComponentScan 사용 가능 |
자동으로 @Aspect , @Component 감지 |
ApplicationContext에 한정 | 각각의 컨텍스트에 선언 필요 |
AspectJ 의존성 필요 | aspectjweaver 라이브러리 필요 |
📌 참고 코드 전체 구조
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ComponentScan("com.intheeast.aspectjsupport.enablingaspectjsupport")
public class AppConfig { }
@Aspect
@Component
public class MyAspect {
@Before("execution(* FooService+.*(..))")
public void advice() {
System.out.println(">>> Advice 실행됨");
}
}
@Component
public class FooService {
public void hello() {
System.out.println("FooService.hello()");
}
}
🔎 Spring AOP 프록시 동작 구조 완전 분석
✅ AOP 프록시의 개념 요약
Spring AOP는 타겟 객체를 감싸는 프록시 객체(proxy)를 생성하여
Advice(공통 관심사)를 중간에 끼워넣는 방식으로 동작합니다.
📌 타겟 객체는 변경되지 않고, 대신 프록시가 모든 메서드 호출을 가로채 처리합니다.
🧭 프록시 동작 흐름도
[클라이언트 코드]
|
v
[프록시 객체 (JDK Proxy or CGLIB Proxy)]
|
v
[Advice (예: @Before, @After)]
|
v
[실제 FooService 객체의 메서드 실행]
🧪 실제 프록시 클래스 확인 방법
Spring에서 생성된 프록시 객체가 실제로 어떤 종류인지 확인하려면AopUtils
유틸리티 클래스를 사용하면 됩니다.
📌 코드 예시
import org.springframework.aop.framework.AopUtils;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
FooService foo = context.getBean(FooService.class);
foo.hello();
System.out.println("\n=== 프록시 타입 확인 ===");
System.out.println("CGLIB 프록시인가? " + AopUtils.isCglibProxy(foo));
System.out.println("JDK 프록시인가? " + AopUtils.isJdkDynamicProxy(foo));
System.out.println("실제 클래스: " + foo.getClass().getName());
}
}
🔍 출력 예시
(1) CGLIB 프록시의 경우 (proxyTargetClass = true
)
>>> Advice 실행됨
FooService.hello()
=== 프록시 타입 확인 ===
CGLIB 프록시인가? true
JDK 프록시인가? false
실제 클래스: com.intheeast.service.FooService$$SpringCGLIB$$abc123
(2) JDK 프록시의 경우 (proxyTargetClass = false
+ 인터페이스 구현)
>>> Advice 실행됨
FooService.hello()
=== 프록시 타입 확인 ===
CGLIB 프록시인가? false
JDK 프록시인가? true
실제 클래스: com.sun.proxy.$Proxy35
🔍 추가로 확인할 수 있는 프록시 동작 요소
✔ 프록시 클래스가 실제 타겟 클래스를 상속했는지?
- CGLIB 프록시는
FooService
를 extends 하여 서브클래스로 만듦 - 따라서
foo instanceof FooService
는 항상true
✔ JDK 프록시는 타겟 클래스의 인터페이스만 구현
foo instanceof FooService
는 ❌ (JDK 프록시는 인터페이스 타입으로만 동작)foo instanceof FooServiceInterface
는 ✅
✅ 실무 팁
상황 | 프록시 전략 추천 |
---|---|
타겟 클래스에 인터페이스가 있음 | JDK 프록시로도 충분 |
타겟 클래스에 인터페이스가 없음 | proxyTargetClass = true 로 CGLIB 사용 |
final 클래스, final 메서드가 많음 |
AOP 적용이 어려움 → 구조 조정 필요 |
@Transactional , @Async 등 AOP 기반 어노테이션 사용 |
동일하게 프록시 기반 적용됨 (주의!) |
✅ 결론
Spring AOP는 항상 프록시 객체를 만들어서 동작합니다.
AOP가 실제 적용되었는지를 확인하고 디버깅할 때는 아래를 체크하세요:
체크 포인트 | 확인 방법 |
---|---|
어떤 프록시 방식인지? | AopUtils.isCglibProxy , isJdkDynamicProxy |
실제 클래스는 무엇인가? | getClass().getName() |
advice가 호출되었는가? | 로그 또는 디버깅 |
내부 자기 호출은 아닌가? | 프록시 우회되므로 AOP 미적용 (주의!) |
🧠 Spring AOP 내부 동작 원리 (자바 기반 구성 기준)
✅ 전체 흐름 요약
@Configuration + @EnableAspectJAutoProxy
↓
Import → AspectJAutoProxyRegistrar
↓
Import → AnnotationAwareAspectJAutoProxyCreator (빈 후처리기)
↓
빈 생성 직후: Advisor/Advice 탐지
↓
ProxyFactory 생성 → 프록시 객체 생성
↓
ApplicationContext에 프록시 등록
🔧 주요 컴포넌트 분석
1️⃣ @EnableAspectJAutoProxy
→ AOP 인프라 등록
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy { ... }
➡️ 이 애노테이션은 AspectJAutoProxyRegistrar
를 통해
Spring AOP의 핵심 후처리기인 AnnotationAwareAspectJAutoProxyCreator
를 등록합니다.
2️⃣ AnnotationAwareAspectJAutoProxyCreator
📌 핵심 클래스. BeanPostProcessor
이자 AopInfrastructureBean
이 클래스는 다음과 같은 역할을 합니다:
기능 | 설명 |
---|---|
BeanPostProcessor |
각 빈이 생성될 때마다 검사 |
AspectAdvisorBuilder 활용 | @Aspect 빈에서 advice 메서드를 읽어 Advisor로 변환 |
wrapIfNecessary() |
AOP 대상이면 ProxyFactory 를 사용해 프록시 생성 |
3️⃣ 프록시 생성 판단 로직 (타겟 클래스 기준)
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// ① 이미 프록시인가?
// ② isInfrastructureClass? → AOP 관련 클래스는 제외
// ③ advisor 존재하는가?
// → 있으면 ProxyFactory로 프록시 생성
}
4️⃣ ProxyFactory
및 AopProxyFactory
프록시를 만들 때 내부적으로 ProxyFactory
가 동작하고,
그 안에서 AopProxyFactory
를 통해 실제 전략을 선택합니다.
if (proxyTargetClass || optimize || noInterfaces) {
return new CglibAopProxy(config);
} else {
return new JdkDynamicAopProxy(config);
}
5️⃣ Advisor
생성과 MethodInterceptor
연결
@Before
→MethodBeforeAdviceInterceptor
@AfterReturning
→AfterReturningAdviceInterceptor
@Around
→AspectJAroundAdvice
Spring은 ReflectiveAspectJAdvisorFactory
를 사용해
각 advice 메서드를 MethodInterceptor로 변환합니다.
6️⃣ 최종 프록시 빈 등록
- 만들어진 프록시는
ApplicationContext
에 원래의 타겟 빈 대신 등록됩니다. - 즉, 이후
getBean()
호출 시 반환되는 객체는 프록시 객체입니다.
🔍 디버깅 팁
1. 어떤 클래스가 프록시되는지 로그로 확인
logging.level.org.springframework.aop=DEBUG
logging.level.org.springframework.beans.factory=DEBUG
2. 프록시 내부 구성 확인
Advised advised = (Advised) context.getBean(FooService.class);
for (Advisor advisor : advised.getAdvisors()) {
System.out.println(advisor.getAdvice().getClass());
}
✅ 마무리 요약
컴포넌트 | 역할 |
---|---|
@EnableAspectJAutoProxy |
AOP 인프라 등록 트리거 |
AnnotationAwareAspectJAutoProxyCreator |
후처리기로 프록시 생성 로직 실행 |
Advisor , Advice , MethodInterceptor |
Aspect 메서드를 프록시 동작으로 연결 |
ProxyFactory |
JDK 또는 CGLIB 프록시 생성 |
ApplicationContext | 최종적으로 프록시 객체를 빈으로 등록 |
이 구조를 이해하시면 Spring AOP가 언제, 어떤 방식으로 작동하는지를 완전히 통제할 수 있게 됩니다.
SpringAOPDemo/src/main/java/com/intheeast/aspectjsupport/enablingaspectjsupport at main · nomadinsunda/SpringAOPDemo
Contribute to nomadinsunda/SpringAOPDemo development by creating an account on GitHub.
github.com
'Spring Framework > Aspect Oriented Programming with Spring' 카테고리의 다른 글
Objenesis (1) | 2025.06.11 |
---|---|
"com.xyz.trading..*" vs "com.xyz.trading.*.*" (0) | 2025.06.11 |
Pointcut Expression 예제들 (0) | 2025.06.07 |
Aspect Oriented Programming with Spring (1) | 2024.11.17 |
Choosing which AOP Declaration Style to Use (0) | 2024.11.17 |