본문 바로가기

Java and Spring

[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 테스트 코드의 실행 순서는 다음과 같다.

  1. Enhancer클래스의 객체를 생성한다. (Proxy객체를 생성해주는 클래스)
  2. enhancer.setSuperClass()를 통해 어떤 구체 클래스를 상속받을지 결정한다.
  3. enhancer.setCallback()의 인자값으로 ConcretService클래스를 전달하였기 때문에 proxy.call() 호출시, ConcretService클래스의 call() 메서드가 호출된다.

 


클래스 의존관계

런타임 의존관계

cglib 테스트 코드를 실행하면 target은 실제 객체가 출력이 되고, proxy는 CGLIB이 생성한 클래스가 출력되게 됩니다.

'Java and Spring' 카테고리의 다른 글

[Spring] 빈 후처리기  (0) 2021.12.11
[Spring] 프록시 팩토리  (0) 2021.12.08
[Java] 동적 프록시(Dynamic Proxy)  (0) 2021.12.01
[Java] ThreadLocal 이해 및 활용  (0) 2021.11.13
[Spring] Lombok 사용법  (0) 2021.11.07