Spring AOP

원종운·2021년 7월 26일
0

Spring AOP는 Dynamic Proxy를 사용하여 AOP를 제공합니다 따라 Spring AOP 를 알아보기전에 Dynamic Proxy 를 위한 JDK Dynamic Proxy 와 CGLIB 를 알아보도록 하겠습니다.

JDK Dynamic Proxy

JDK Dynamic Proxy는 Reflection를 이용하여 런타임시에 동적으로 만든 프록시 객체를 의미합니다. JDK Dynamic Proxy는 인터페이스를 기반으로 프록시 객체를 생성합니다.

JDK Dynamic Proxy는 프록시 객체 생성를 사용할 때 InvocationHandler 인터페이스를 구현한 구현체를 매핑하여줍니다. InvocationHandler 인터페이스는 invoke 메서드를 오버라이딩하여 프록시 객체가 수행하여할 작업을 위임받습니다.

invoke 메서드는 프록시 객체의 메소드가 호출될 때 호출되어집니다.

InvocationHandler 인터페이스의 장점은 프록시 객체가 수행해야할 작업에 대한 부가기능을 한 곳에 작성함으로서 코드의 중복을 막을 수 있다는 점입니다. 추가로 넘어온 Method 객체를 통하여 프록시 객체의 기능상의 제약을 걸 수도 있습니다.

invoke 메서드 파라미터 설명

  1. proxy : 생성된 프록시 객체
  2. method : 프록시 객체의 메서드 호출에 대한 정보를 담은 Method 객체
  3. args : 프록시 객체의 메소드 호출에 전달된 인자들

반환값 설명

  • 프록시 객체에서 호출된 메서드의 반환값
  • 일반적으로 InvocationHandler을 구현할 때 프록시 객체의 실제 타겟 객체를 넘겨받게 되고, 해당 객체와 Method 객체의 invoke 메서드를 함께 이용하여 타겟 객체의 메소드를 알맞게 호출하여 결과를 반환합니다.

문제점

  • 인터페이스가 반드시 정의되어있어야하며 이는 인터페이스 선언에 강제성을 부여하게 됩니다.
  • Reflection을 사용하기때문에 퍼포먼스상 이슈가 있습니다.

CGLIB

Code Generator Library의 약어로 클래스의 바이트코드를 조작하여 프록시 객체를 생성하여주는 오픈소스 라이브러리입니다. CGLIB의 Enhancer 클래스를 이용하여 프록시 객체를 생성하며, JDK Dyanmic Proxy와는 다르게 기반 인터페이스가 없어도 프록시 객체를 생성할 수 있습니다.

Enhancer 클래스를 통하여 프록시 객체를 생성하기 위해서는 타겟 클래스(SuperClass로 전달)와 JDK Dynamic Proxy 에서의 InvocationHandler 인터페이스와 동일한 역할을 수행하는 MethodInterceptor 인터페이스를 구현한 구현체를 아래와 같이 전달(Callback으로 전달)해줘야합니다.

intercept 메서드는 프록시 객체의 메소드가 호출될 때 호출되어집니다.

invoke 메서드 파라미터 설명

  1. obj : 원본 객체
  2. method : 원본 객체의 메서드 호출에 대한 정보를 담은 Method 객체
  3. args : 프록시 객체의 메소드 호출에 전달된 인자들
  4. proxy : 원본 객체의 메소드 프록시

반환 값 설명

  • InvocationHandler 인터페이스의 invoke 메서드와 동일합니다.
  • method 인자를 사용하여 원본 객체의 메소드 호출을 할 경우 리플렉션을 사용하게 되어 퍼포먼스상 문제가 있을 수 있어 주로 메소드 프록시인 proxy 인자를 사용하여 원본 객체 메소드를 호출합니다.

CGLIB는 타겟 클래스를 상속받아 타킷 클래스에 포함된 모든 메소드를 재정의하여 프록시 객체를 생성하여줍니다. 하지만 상속을 기반으로 프록시 객체를 생성하기때문에 상속의 제약사항을 그대로 따르게 된다. final 이나 private와 같이 상속에서 오버라이딩이 불가능한 경우는 프록시 객체를 생성할 수 없습니다.

문제점

  • net.sf.cglib.proxy.Enhancer 의존성 추가
  • 디폴트 생성자 필수
  • 타겟 클래스 생성자 2회 호출

Enhancer 의존성 추가

  • CGLIB는 오픈 소스 라이브러리이므로, Spring이 공식적으로 지원하는 방식이 아니기때문에 의존성을 추가하여 사용하였으나 Spring이 채택하여 내부 코어모듈로 흡수됨으로서 해결되었습니다.

디폴트 생성자 필수, 타겟 클래스 생성자 2회 호출

  • objenesis 라이브러리를 이용하여 디폴트 생성자 필수, 타겟 클래스 생성자 2회 호출의 문제점을 해결하였습니다. 타겟 클래스 생성자가 2회 호출 되는 이유가 궁금하다면 Class.newInstance() 를 참고, 추가로 objenesis 라이브러리가 무엇인지 궁금하면 여기를 클릭

작동 원리와 퍼포먼스

CGLIB는 메서드가 처음 호출될 때 동적으로 타겟 클래스의 바이트 코드를 조작하여 프록시 객체를 생성합니다. 또한 이때 생성된 바이트 코드를 이후 호출에서는 재사용합니다.

JDK Dynamic Proxy와 달리 바이트 코드를 조작하는 방법을 사용한다는 점과, 조작하여 생성된 바이트 코드를 재활용하기때문에 퍼포먼스가 JDK Dynamic Proxy 보다 좋습니다.

Spring AOP

Spring은 프록시를 이용하여 기존 프록시의 문제를 해결하기 위하여 런타임 중 동적으로 프록시를 생성하는 Dynamic Proxy 를 사용합니다. Dynamic Proxy 를 구현하기 위한 방법으로는 앞서 살펴본 JDK Dynamic Proxy 와 CGLIB 가 있으며 Spring 의 경우는 두 가지 방법 모두 사용합니다.

AOP의 적용 대상이 인터페이스거나 인터페이스를 구현하고 있는 구현체인지 아닌지에 따라 Spring은 프록시 객체를 JDK Dynamic Proxy 로 생성할지 CGLIB로 생성할지를 결정합니다.

인터페이스이거나 인터페이스를 구현하고 있는 구현체의 경우는 아래의 사진처럼 JDK Dynamic Proxy 를 사용하고 그렇지 않은 경우, 즉 인터페이스가 존재하지 않는 클래스의 경우는 CGLIB를 사용합니다만 앞서 말한 CGLIB는 문제점이 존재하므로 Spring 은 이러한 문제점을 해결하기 위하여 objenesis 라이브러리와 함께 CGLIB를 사용합니다.

이러한 Spring AOP 방식은 동적으로 프록시 객체를 생성하여 구현되므로 런타임 위빙에 해당하며, Spring은 이러한 런타임 위빙뿐만 아니라 컴파일 위빙을 사용하는 AspectJ 라는 AOP 또한 지원합니다.

참고 자료

profile
Java, Python, JavaScript Lover

0개의 댓글