개요
Spring Framework 를 사용할 때 AOP 는 필수라고 말해도 과언이 아니다. Logging, Transaction Management, Caching, Security 등등 에서 주로 사용된다. 그렇다면 Spring AOP 는 어떻게 동작할까? 이번 글에서 정리를 해보려고 한다.
Spring 에서 AOP 는 JDK Dynamic Proxy 혹은 CGLIB Dynamic Proxy 를 사용하여 Proxy 를 생성하여 관리한다.
Proxy target object 가 하나 이상의 interface 를 구현하는 경우에는 JDK Dynamic Proxy 가 사용된다. 반대로 target object 가 interface 를 구현하지 않는다면 CGLIB proxy 가 생성된다. 하지만 proxy-target-class=true 로 설정한다면 interface 를 구현하더라도 CGLIB proxy 가 사용된다.
CGLIB Proxy 를 사용할 때 고려해야 할 몇가지가 있다.
- final method 는 overriding 이 불가능하기 때문에 advice 적용이 불가능하다.
- target class 가 final 일 경우 불가능하다.
- Spring 3.2 version 전의 경우 CGLIB 와 관련된 jar 를 classpath 에 추가해주어야 한다. 3.2 version 부터는 org.springframework.cglib 로 repackaged 되어 spring-core jar 안에 포함이 되었다.
- Spring 4.0 version 전의 경우 proxy target object 의 생성자는 두 번 호출되었다. 또한 기본 생성자가 필수였다. 4.0 version 이후부터는 Objenesis 를 통해서 proxy instance 가 생성될 때 더이상 생성자가 두 번 호출되지 않게 되었다. 일반적이지 않은 경우이지만 4.0 version 전에서는 proxy target object 의 생성자에 주요한 로직을 작성할 경우 생성자가 두 번 호출되기 때문에 이슈가 발생할 수 있었다.
추가적으로 언급하자면 Hibernate Proxy 는 CGLIB Proxy 를 사용한다. 그리고 CGLIB Proxy 와 JDK Proxy 를 성능상에서 비교했을 때, CGLIB Proxy 가 우수하다.
JDK Proxy 와 CGLIB Proxy 동작방식과 차이점
두 가지 proxy 를 다루는 방법에서 어떠한 차이가 있는지 확인해보자.
JDK Proxy
jdk proxy 는 reflection 에 기반해 동작한다. reflection 성능측면에서 좋지 않다.
java.lang.reflect.Proxy.newProxyInstance() 메서드를 통해서 target object 의 Proxy 객체를 생성한다. 인자로는 ClassLoader, Interface array, java.lang.reflect.InvocationHandler 를 구현한 객체를 받는다.
newProxyInstance() 시그니쳐를 통해서 확인할 수 있듯이, JDK Proxy 는 interface 가 필요하다. 작성한 예제에서는 BookService 라는 interface 를 인자로 받고 있으며, BookService interface 구현한 BookServiceImpl 를 주입받는 BookServiceInvocationHandler 를 통해서 실제 method 에 advice 가 적용된다. BookServiceInvocationHandler 는 InvocationHandler Interface 를 구현한다. InvocationHandler 의 invoke method 를 구현함으로써 before 와 after 를 적용하고 있다.
한가지 주목할 점이 있다. BookServiceImpl 에서 create() 메서드 안에서 delete 메서드를 호출하고 있다. 이 때 내부에서 호출된 delete 메서드는 advice 가 적용되지 않는다. 이유는 InvocationHandler 의 invoke method 를 통해서 advice 가 적용되기 때문이다.
CGLIB Dynamic Proxy
CGLIB 의 약자는 (Byte) Code Generator Library 의 약자이다. (Byte) 코드를 생성해주는 라이브러리로 이해할 수 있다. Compile 이후 Runtime 에 동적으로 target class 혹은 interface 를 상속하여 Proxy Byte code 를 생성해준다.
Enhancer 인스턴스에 setSuperclass() 메서드에 AlbumService class 를 설정하고 있다. super class 라는 키워드에서 짐작할 수 있듯이, Proxy 를 생성할 때 target class 인 AlbumService 를 상속을 통해서 Proxy 를 생성한다.
추가적으로 setCallbacks() 와 setCallbackFilter() 를 설정하고 있는 것을 확인할 수 있다.
CallbackFilter 의 accept() 는 Proxy 가 method 를 호출하기 전에 어떤 Callback 을 통해서 진행할 지 결정하는 역할을 한다.
AlbumServiceCallbackFilter 에서는 method 이름에 create 를 포함한다면 0 을 delete 를 포함하고 있다면 1을 나머지는 2를 반환한다.
여기서 0,1,2 값은 setCallbacks() 에 인자의 Callback[] array 의 index 이다.
따라서 AlbumService 의 create 메서드는 CreateAlbumMethodInterceptor 를 통해서 advice 를 적용하고
delete 메서드는 DeleteAlbumMethodInterceptor 를 통해서 advice 를 적용한다.
그리고 나머지 메서드는 NoOp.INSTANCE 로 어떤 MethodInterceptor 도 적용하지 않는다는 의미이다.
Callback 은 MethodInterceptor 의 intercept() 를 통해서 advice 가 적용된다.
실행결과에서 주목해야 할 점은 CGLIB Proxy 는 JDK Proxy 와는 다르게 create() 메서드에서 내부적으로 delete() 메서드를 호출할 때 advice 가 적용된다는 것이다.
Spring Boot AOP
spring boot 2.4.4 version 기준으로 AopAutoConfiguration 을 확인해보자.
proxy-target-class 가 true 로 설정할 경우 CGLIB Proxy 를 false 일 경우 JDK Proxy 를 사용하도록 설정할 수 있다.
ide 도움을 받아 application.yml 을 설정할 때 확인할 수 있다. default 로 cglib proxy 가 설정되어있다.
참조
댓글
댓글 쓰기