CGLIB : Code Generator Library
- CGLIB은 바이트 코드를 조작해서 동적으로 클래스를 생성하는 기술을 제공하는 라이브러리이다.
- CGLIB를 사용하면 인터페이스가 없어도 구체 클래스만 가지고 동적 프록시를 만들어낼 수 있다.
- CGLIB는 원래 외부 라이브러리인데, 스프링 프레임워크가 스프링 내부 소스 코드에 포함했다. 따라서 스프링을 사용한다면 별도의 외부 라이브러리를 추가하지 않아도 사용할 수 있다.
CGLIB을 스프링의 'ProxyFactory'가 편리하게 사용하도록 제공하므로, 예제 코드로 CGLIB을 간단하게만 이해해보자.
먼저 아래는 정말 간단한 서비스 코드이다.(즉 비즈니스 로직이라고 가정한다.)
@Slf4j
public class ConcreteService {
public void call() {
log.info("ConcreteService 호출");
}
}
JDK 동적 프록시에서는 InvocationHandler를 implements한 클래스에 추가 기능 로직을 넣었다면
CGLIB에서는 MethodInterceptor를 implements하여 추가 기능 로직을 넣는다( 해당 예시는 비즈니스 로직 수행시간 측정).
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.proxy.MethodInterceptor;//springframework.cglib을 쓰자
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
@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("Time Proxy 실행");
long startTime = System.currentTimeMillis();
Object result = methodProxy.invoke(target, args);//method.invoke()보다 성능이 더 좋음
long endTime = System.currentTimeMillis();
log.info("TimeProxy 종료 resultTime = {}", endTime-startTime);
return result;
}
}
이제 이를 테스트해보자.
import hello.proxy.cglib.code.TimeMethodInterceptor;
import hello.proxy.common.ConcreteService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.cglib.proxy.Enhancer;
@Slf4j
public class CGLIBtest {
@Test
void cglib() {
ConcreteService target = new ConcreteService();
Enhancer enhancer = new Enhancer();//Enhancer가 CGLIB를 만들어 준다.
enhancer.setSuperclass(ConcreteService.class);//해당 인자를 상속받아 프록시를 만든다.
enhancer.setCallback(new TimeMethodInterceptor(target));
ConcreteService proxy = (ConcreteService) enhancer.create();
proxy.call();
log.info("targetClass = {}", target.getClass());
log.info("proxyClass = {}", proxy.getClass());
}
}
실행결과:
Time Proxy 실행
ConcreteService 호출
TimeProxy 종료 resultTime = 9
targetClass = class hello.proxy.common.ConcreteService
proxyClass = class hello.proxy.common.ConcreteService$$EnhancerByCGLIB$$1c94e8ce//CGLIB$$가 붙음
• Enhancer : CGLIB는 Enhancer 를 사용해서 프록시를 생성한다.
• enhancer.setSuperclass(ConcreteService.class) : CGLIB는 구체 클래스를 상속 받아서 프록시를 생성할 수 있다. 어떤 구체 클래스를 상속 받을지 지정한다.
• enhancer.setCallback(new TimeMethodInterceptor(target)) :프록시에 적용할 실행 로직을 할당한다. enhancer.create() : 프록시를 생성한다. 앞서 설정한 enhancer.setSuperclass(ConcreteService.class) 에서 지정한 클래스를 상속 받아서 프록시가 만들어진다.
CGLIB으로 만든 프록시와 클래스들의 관계를 그림으로 살펴보자.
그림에서 보듯 CGLIB 동적 프록시는 ConcreteService를 상속받아서 만들어진다. 그리고 만들어진 CGLIB 동적 프록시는 뒤에서 MethodInterceptor를 뒤에서 호출해준다.
런타임에서는 아래와 같다.
클라이언트는 CGLIB이 만들어준 프록시를 호출하고, 해당 프록시는 Interceptor를, 그리고 Interceptor는 ConcreteService(target)을 호출하게 된다.
CGLIB의 제약
클래스 기반 프록시는 상속을 사용하기 때문에 몇가지 제약이 있다.
1. 부모 클래스의 생성자를 체크해야 한다. -> CGLIB는 자식 클래스를 동적으로 생성하기 때문에 기본 생성자가 필요하다.
2. 클래스에 final 키워드가 붙으면 상속이 불가능하다. -> CGLIB에서는 예외가 발생한다.
3. 메서드에 final 키워드가 붙으면 해당 메서드를 오버라이딩 할 수 없다. -> CGLIB에서는 프록시 로직이 동작하지 않는다.
참고
CGLIB를 사용하면 인터페이스가 없는 V2 애플리케이션에 동적 프록시를 적용할 수 있다. 그런데 지금 당장 적용하기에는 몇가지 제약이 있다. V2 애플리케이션에 기본 생성자를 추가하고, 의존관계를 setter 를 사용해서 주입하면 CGLIB를 적용할 수 있다. 하지만 다음에 학습하는 ProxyFactory 를 통해서 CGLIB를 적용하면 이런 단점을 해결하고 또 더 편리하기 때문에, 애플리케이션에 CGLIB로 프록시를 적용하는 것은 조금 뒤에 알아보겠다.
김영한님의 인프런 강의와 PDF를 바탕으로 정리하였습니다.
'Spring > spring AOP' 카테고리의 다른 글
스프링 코어 2 - 포인트 컷, 어드바이스, 어드바이저 (0) | 2022.01.10 |
---|---|
스프링 코어 2 - 스프링이 지원하는 프록시(프록시 팩토리) (0) | 2022.01.10 |
스프링 코어2 - JDK 동적 프록시 (0) | 2022.01.09 |
스프링 코어2 - 리플렉션 (0) | 2022.01.09 |
스프링 코어2 - 구체 클래스 기반 프록시 (0) | 2022.01.08 |