목차
- Spring 핵심원리 고급편 - CGLIB
- Spring 핵심원리 고급편 - Dynamic Proxy 2
- Spring 핵심원리 고급편 - Dynamic Proxy 1
- Spring 핵심원리 고급편 - 리플렉션
- Spring 핵심원리 고급편 - 구체 클래스 기반 프록시 적용 2
- Spring 핵심원리 고급편 - 구체 클래스 기반 프록시
- Spring 핵심원리 고급편 - 인터페이스 프록시 1
- Spring 핵심원리 고급편 - Decorator Pattern 2
- Spring 핵심원리 고급편 - Decorator Pattern 1
- Spring 핵심원리 고급편 - Proxy 패턴 컴포넌트 스캔으로 자동 빈 등록
- Spring 핵심원리 고급편 - Proxy 패턴 인터페이스 없는 없는 구체 클래스
- Spring 핵심원리 고급편 - Proxy 패턴 인터페이스와 구체 클래스
- Spring 핵심원리 고급편 - Proxy 패턴
- Spring 핵심원리 고급편 - Strategy 패턴
- Spring 핵심원리 고급편 - Template Method 패턴
참고
Dynamic Proxy 의 한계와 CGLIB
Dynamic Proxy 의 경우 인터페이스를 구현한 클래스에 대해서만 프록시 객체를 생성할 수 있습니다. 그렇다면 인터페이스가 없는 클래스의 경우 프록시 생성을 못하냐? 정답은 아닙니다.
자바에서는 인터페이스를 구현하지 않은 클래스에 대해서도 프록시 객체를 생성할 수 있도록 CGLIB 를 제공합니다. 차이점은 Dynamic Proxy 는 인터페이스 구현을 통해 프록시 객체를 생성하지만 CGLIB 는 상속 을 통해 프록시 객체를 생성한다는 것입니다.
CGLIB 란?
CGLIB 는 Code Generation Library의 약자로 런타임 시 Java 바이트 코드 를 조작해 동적으로 클래스를 생성하는 라이브러리다. JDK Dynamic 프록시와는 다르게 구체 클래스 만 갖고도 동적으로 Proxy 를 생성할 수 있다.
CGLIB은 자바의 리플렉션(Reflection) API 와 바이트코드 조작(Bytecode Manipulation) 기술을 이용하여 클래스의 상속 구조를 이용해서 프록시 객체를 생성합니다. 이 과정에서 바이트코드를 조작하므로, JVM의 클래스 로딩 과정에서 원본 클래스의 바이트코드를 변경할 수 있습니다.
CGLIB을 이용하여 프록시 객체를 생성할 때는 Enhancer 클래스를 이용합니다. Enhancer 클래스는 동적으로 클래스를 생성할 때 필요한 정보를 받아서 CGLIB이 제공하는 Callback 인터페이스 를 이용해서 프록시 객체를 생성합니다.
MethodInterceptor 인터페이스 - 부가기능 로직 작성
CGLIB 는 MethodInterceptor 를 이용해 JDK Dynamic Proxy 의 InvocationHandler 처럼 공통 로직 을 작성한다.
MethodInterceptor 인터페이스의 intercept 메소드는 프록시 객체가 호출될 때, 요청을 가로채 호출된 메소드 대신 실행됩니다. 이를 이용하여, 메소드 호출 전후에 로그를 남기거나, 메소드 호출 시간을 측정하거나, 트랜잭션을 관리하는 등의 작업을 수행할 수 있습니다.
MethodInterceptor 인터페이스는 자바의 다이나믹 프록시에서 제공하는 InvocationHandler와 유사한 역할을 합니다. 그러나 CGLIB은 상속 기반의 프록시 객체를 생성하므로, MethodInterceptor를 이용하여 원본 클래스의 메소드를 호출할 때, super 키워드를 이용하여 부모 클래스의 메소드를 호출할 수 있습니다.
Object object
: CGLIB 가 적용된 객체Method method
: 호출된 메소드Object[] args
: 메서드를 호출하면서 전달된 인수MethodProxy proxy
: 메서드 호출에 사용하는 객체
public interface MethodInterceptor extends Callback { |
부가기능 로직 작성
method.invoke(target, args)
를 이용해 원본 클래스의 메소드를 호출하던 방식과 달리, CGLIB 은 methodProxy.invokeSuper(target, args)
를 이용해 원본 클래스의 메소드를 호출합니다.
method.invoke(target, args)
로 실제 메소드를 호출해도 로직은 작동하지만 methodProxy.invokeSuper(target, args)
방식으로 호출하는게 성능상의 이접이 있다고 하니 참고하도록 하자!
|
Enhancer - 프록시 객체 생성
CGLIB 에서는 Enhancer 를 이용해 Proxy 객체를 생성한다.
원본 클래스
|
CGLIB 프록시 객체 생성 테스트 코드
CGLIB 에서 프록시 객체를 생성하고자 할 때, Enhancer 를 이용해 프록시 객체를 생성합니다.
Enhancer 객체 setSuperclass 메소드를 이용해 프록시 객체를 생성할 원본 클래스를 지정하고 setCallback 메소드를 이용해 프록시 객체에 부가기능로직이 구현된 MethodInterceptor 객체를 지정합니다.
Enhancer 객체에 원본 클래스와 부가기능이 정의되면 create 메소드를 이용하여 프록시 객체를 생성할 수 있습니다.
|
테스트 결과
Enhancer 클래스를 통해 CGLIB 프록시 객체가 생성된 것을 확인할 수 있습니다. 프록시 객체의 메소드가 호출되면, 부가기능이 실행되는 것을 확인할 수 있습니다.
23:52:18.794 [Test worker] INFO hello.proxy.cglib.CglibTest - targetClass = class hello.proxy.common.service.ConcreteService |
CGLIB 장단점
CGLIB 을 이용한 프록시 객체 생성 방식은 다이나믹 프록시보다 더 빠른 속도를 보이며, 인터페이스를 구현하지 않은 클래스에 대해서도 프록시 객체를 생성할 수 있습니다. 하지만, 바이트코드를 조작하기 때문에 보안 검사 등에서 문제가 될 수 있습니다. 그리고 원본 클래스의 생성자나 private 메서드 등은 프록시 객체에서 호출할 수 없습니다.