About Spring AOP JDK Dynamic Proxy and CGLIB Dynamic Proxy

개요

 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 가 설정되어있다.





참조














댓글

이 블로그의 인기 게시물

About JVM Warm up

About idempotent

About Kafka Basic

About ZGC

sneak peek jitpack

Spring Boot Actuator readiness, liveness probes on k8s

About Websocket minimize data size and data transfer cost on cloud

About G1 GC

대학생 코딩 과제 대행 java, python, oracle 네 번째