[TIL] Spring AOP - cglib, jdk프록시, aspectJ
Spring AOP와 AspectJ의 차이
Spring AOP 기본 동작
- CGLIB: 클래스 상속을 통해 프록시 생성,
final 메서드에 AOP 적용 불가.
- JDK 동적 프록시: 인터페이스 기반 프록시. 인터페이스가 없으면 CGLIB 사용.
- 프록시 생성 조건:
- 인터페이스 존재 시: JDK 동적 프록시 사용.
- 인터페이스 없음: CGLIB 사용.
CGLIB AOP
- 장점:
- 인터페이스가 없는 클래스에도 프록시 적용 가능.
- 모든 메서드에 AOP 적용 가능.
- 단점:
final 메서드에 AOP 적용 불가.
final 메서드 호출 시 AOP가 적용되지 않음.
- 디버깅 방법:
CacheInterceptor, CacheAspectSupport 디버깅.
ProxyFactory와 SimpleKeyGenerator 사용으로 프록시 흐름 및 캐시 동작 분석.
AspectJ
- 컴파일 시점 위빙 (Compile-Time Weaving):
- 소스 코드를 컴파일할 때 Aspect 코드를 클래스에 삽입.
ajc(AspectJ Compiler) 사용.
- 로드 시점 위빙 (Load-Time Weaving):
- 클래스 로딩 시 AOP 로직을 삽입.
- JVM 클래스 로더와 AspectJ 에이전트 사용.
- 장점:
final, 생성자, 정적 메서드에도 AOP 적용 가능.
- 더 강력하고 정교한 AOP 지원.
- 단점:
- 설정 복잡도 증가.
- 빌드 및 실행 환경 설정 필요.
- 성능 오버헤드 발생 가능.
Spring AOP vs AspectJ
- Spring AOP:
- 프록시 기반 (동적 프록시/JDK 프록시 또는 CGLIB).
- 간편한 설정 및 사용.
- 성능 최적화 가능하지만,
final 메서드에 AOP 적용 불가.
- AspectJ:
- 바이트코드 조작 (컴파일 시점/로드 시점 위빙).
- 복잡한 설정이 필요하지만, 강력하고 정교한 AOP 제공.
final 메서드와 같은 제한 없는 AOP 지원.
CGLIB 프록시와 final 메서드
- 문제점:
final 메서드는 CGLIB 상속 기반에서 오버라이딩 불가.
- 해결:
final 메서드가 많은 경우 AOP 적용 불가.
- 경고 메시지: "Unable to proxy method [methodName] because it is final."
- 해결 방법:
- AOP Proxy 설정 강제화:
spring.aop.proxy-target-class=false로 JDK 동적 프록시 강제 설정.
- 인터페이스 사용:
final 메서드를 피하거나, 인터페이스를 사용하여 JDK 동적 프록시 적용.
AspectJ의 최적화된 AOP 처리
- 장점:
- 바이트코드를 직접 수정하여
final 메서드에도 AOP 적용.
- 정밀한 Join Point 지정:
within, call, get 등 다양한 포인트컷 지원.
- 단점:
- 설정 복잡성:
@EnableAspectJAutoProxy 설정, aspectjweaver 의존성 추가.
- 디버깅 어려움: 바이트코드 수정으로 코드 흐름 파악 어려움.
Spring의 기본 AOP 설정
-
ProxyFactory:
- 인터페이스 기반 프록시 또는 CGLIB 프록시 자동 선택.
- 구분 이유: 인터페이스가 있는 클래스는 JDK 동적 프록시 사용, 없으면 CGLIB 사용.
- 인터페이스 기반 jdk프록시는 간단한 구현/유지보수에 장점, cglib은 jdk프록시 대비 빠른 성능에 장점. 이에 혼용.
-
AspectJ 사용 여부:
@EnableAspectJAutoProxy(proxyTargetClass=false) 설정을 통해 AspectJ 사용.
- AspectJ로의 전환은 추가적인 컴파일러와 설정이 필요.
기타 AOP 구현 방식
- ProxyFactory와 AspectJ 차이점:
- AspectJ는 바이트코드 수준에서 직접 AOP 로직을 삽입.
- ProxyFactory는 프록시 객체를 통해 메서드 호출을 가로채어 AOP 적용.
성능 저하
- 바이트코드 수정이 성능 저하를 초래하는 이유:
- 런타임에 클래스를 동적으로 수정하기 때문에 추가적인 작업이 필요.
- 클래스 로딩과 메서드 호출 시 추가 오버헤드 발생.
JVM 제약
- final 메서드는 JVM에서 상속 불가로 설계되어, AOP가 적용되지 않음.
- CGLIB가
final 메서드를 처리하지 않는 이유:
final 메서드는 변경이 불가능하므로 안정성과 보안을 유지하기 위해 AOP가 적용되지 않음.
바이트코드 조작
- ASM, Javassist 라이브러리를 사용하여 바이트코드를 동적으로 수정.
- 방법:
final 메서드에서 final 키워드를 제거하고, 필요한 경우 새 메서드를 오버라이드.
- 제약: 런타임 성능 저하, 보안 제한 등.
왜 Spring은 CGLIB를 사용했나?
- 유연성: 컴파일 타임 변경 없이 동적으로 AOP를 적용.
- 설정 편의성: CGLIB은 별도의 설정 없이 동작.
- 성능 비용이 적당한 수준: 대규모 애플리케이션에서는 대부분 큰 성능 저하 없음.
AspectJ 사용
- final 메서드 AOP 처리:
- AspectJ는 바이트코드 조작을 통해
final 메서드에 AOP 적용.
- AspectJ의 장점:
- 모든 메서드와 생성자, 정적 메서드,
final 메서드에도 AOP를 적용할 수 있음.
- 단점:
AspectJ 학습 곡선
- Pointcut 표현식:
execution, within, @annotation 등을 사용하여 AOP 적용 대상을 정의.
- Advice 유형:
@Before, @After, @Around 등.
- Spring과의 통합:
@EnableAspectJAutoProxy와 aspectjweaver 설정 필요.
- 디버깅: 바이트코드가 수정되어 디버깅이 어려울 수 있음.