[Java] CGLIB(Code Generator Library)
이번 글에서는 CGLIB의 개념과 사용방법을 간단하게 알아보려고 합니다.
CGLIB(Code Generator Library)과 동적 프록시(Dynamic Proxy)
CGLIB은 이전 포스팅에서 정리했던 동적 프록시와 거의 동일한 패턴으로 프록시를 사용한 기술 중 하나입니다.
[Java] 동적 프록시(Dynamic Proxy)
이번 글에서는 동적 프록시(Dynamic Proxy)가 왜 필요하고 어떻게 사용되는지 알아보려 합니다. 동적 프록시(Dynamic Proxy)와 프록시(Proxy) 동적 프록시가 왜 필요한지 알기위해서는 그전에 프록시가 사
gong-story.tistory.com
동적 프록시는 인터페이스가 있어야 프록시 클래스의 자동생성이 가능하였지만, 이번 시간에 공부할 CGLIB은 인터페이스가 없어도 자동으로 프록시 클래스를 만들어 줄 수 있습니다.
심지어, CGLIB은 바이트코드를 조작하여 프록시 클래스를 생성해주기 때문에 동적 프록시보다 성능적으로도 좋다고 합니다. 그럼 CGLIB은 어떻게 프록시 클래스를 만들어 주는지 알아보도록 하겠습니다.
new Enhancer()
CGLIB은 Enhancer클래스를 통해 Proxy클래스를 생성 할 수 있습니다.
@Test
void cglib() {
ConcreteService target = new ConcreteService();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ConcreteService.class);
enhancer.setCallback(new TimeMethodInterceptor(target));
ConcreteService proxy = (ConcreteService) enhancer.create();
}
- Enhancer : CGLIB은 Enhancer클래스를 통해 proxy를 생성한다.
- enhancer.setSuperClass() : 어떤 구체클래스를 상속 받을지 지정한다.
- enhancer.setCallback() : 프록시에서 실행할 로직을 지정한다.
- enhancer.create() : enhancer.setSuperClass()에서 지정한 클래스를 상속받아 프록시를 생성한다.
MethodInterceptor
MethodInterceptor는 intercept() 메서드만 가지고 있는 인터페이스 입니다.
동적 프록시에서 실행로직을 위해 InvocationHandler 인터페이스를 사용했다면, CGLIB은 실행로직을 위해MethodInterceptor 인터페이스를 사용합니다.
public interface MethodInterceptor extends Callback {
Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}
- Object : CGLIB에 적용된 객체
- Method : 호출된 메서드
- Object[] : 메서드를 호출하면서 전달된 인수
- MethodProxy : 메서드 호출에 사용
동적 프록시에서 사용된 InvocationHandler 인터페이스에는 4번째 인자인 MethodProxy가 없었는데, CGLIB에는 MethodProxy가 나타났습니다. 동적 프록시처럼 method를 활용하여 로직을 호출 할 수도 있지만, CGLIB은 MethodProxy를 통해 로직을 호출하면 좀 더 빠르다고하여 MethodProxy를 통해 호출하는 것을 권장한다고 합니다.
예제코드
@Test
void cglib() {
ConcreteService target = new ConcreteService();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ConcreteService.class);
enhancer.setCallback(new TimeMethodInterceptor(target));
ConcreteService proxy = (ConcreteService) enhancer.create();
log.info("targetClass={}", target.getClass());
log.info("proxyClass={}", proxy.getClass());
proxy.call();
}
@Slf4j
public class ConcreteService {
public void call() {
log.info("ConcreteService 호출");
}
}
@Slf4j
public class TimeMethodInterceptor implements MethodInterceptor {
private final Object target;
public TimeMethodInterceptor(Object target) {
this.target = target;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
log.info("TimeProxy 실행");
long startTime = System.currentTimeMillis();
Object result = methodProxy.invoke(target, args);
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("TimeProxy 종료 resultTime={}", resultTime);
return result;
}
}
cglib 테스트 코드의 실행 순서는 다음과 같다.
- Enhancer클래스의 객체를 생성한다. (Proxy객체를 생성해주는 클래스)
- enhancer.setSuperClass()를 통해 어떤 구체 클래스를 상속받을지 결정한다.
- enhancer.setCallback()의 인자값으로 ConcretService클래스를 전달하였기 때문에 proxy.call() 호출시, ConcretService클래스의 call() 메서드가 호출된다.
![]() 클래스 의존관계 |
![]() 런타임 의존관계 |
cglib 테스트 코드를 실행하면 target은 실제 객체가 출력이 되고, proxy는 CGLIB이 생성한 클래스가 출력되게 됩니다.