[TIL] Spring AOP - cglib, jdk프록시, aspectJ

Soeng_dev·2024년 12월 20일

Spring AOP와 AspectJ의 차이

Spring AOP 기본 동작

  • CGLIB: 클래스 상속을 통해 프록시 생성, final 메서드에 AOP 적용 불가.
  • JDK 동적 프록시: 인터페이스 기반 프록시. 인터페이스가 없으면 CGLIB 사용.
  • 프록시 생성 조건:
    • 인터페이스 존재 시: JDK 동적 프록시 사용.
    • 인터페이스 없음: CGLIB 사용.

CGLIB AOP

  • 장점:
    • 인터페이스가 없는 클래스에도 프록시 적용 가능.
    • 모든 메서드에 AOP 적용 가능.
  • 단점:
    • final 메서드에 AOP 적용 불가.
    • final 메서드 호출 시 AOP가 적용되지 않음.
  • 디버깅 방법:
    • CacheInterceptor, CacheAspectSupport 디버깅.
    • ProxyFactorySimpleKeyGenerator 사용으로 프록시 흐름 및 캐시 동작 분석.

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과의 통합: @EnableAspectJAutoProxyaspectjweaver 설정 필요.
  • 디버깅: 바이트코드가 수정되어 디버깅이 어려울 수 있음.
profile
Software Engineer

0개의 댓글