@EnableAspectJAutoProxy

2025. 6. 9. 21:26Spring Framework/Aspect Oriented Programming with Spring

🌿 Spring AOP @EnableAspectJAutoProxy

@EnableAspectJAutoProxySpring 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에만 적용됩니다.
  • 웹 애플리케이션에서 RootContextDispatcherServlet 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 프록시는 FooServiceextends 하여 서브클래스로 만듦
  • 따라서 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️⃣ ProxyFactoryAopProxyFactory

프록시를 만들 때 내부적으로 ProxyFactory가 동작하고,
그 안에서 AopProxyFactory를 통해 실제 전략을 선택합니다.

if (proxyTargetClass || optimize || noInterfaces) {
    return new CglibAopProxy(config);
} else {
    return new JdkDynamicAopProxy(config);
}

 

5️⃣ Advisor 생성과 MethodInterceptor 연결

  • @BeforeMethodBeforeAdviceInterceptor
  • @AfterReturningAfterReturningAdviceInterceptor
  • @AroundAspectJAroundAdvice

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가 언제, 어떤 방식으로 작동하는지를 완전히 통제할 수 있게 됩니다.

 

GitHub: https://github.com/nomadinsunda/SpringAOPDemo/tree/main/src/main/java/com/intheeast/aspectjsupport/enablingaspectjsupport

 

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