CGLIB

현시기얌·2021년 11월 22일
0

AOP

목록 보기
3/19

CGLIB

CGLIB는 바이트코드를 조작해서 동적으로 클래스를 생성하는 기술을 제공하는 라이브러리다.
CGLIB를 사용하면 인터페이스가 없어도 구체 클래스만 가지고 동적 프록시를 만들어낼 수 있다.
CGLIB는 원래 외부 라이브러리인데, 스프링 프레임워크가 스프링 내부 소스 코드에 포함했기 때문에 스프링을 사용한다면 별도의 외부 라이브러리를 추가하지 않아도 사용할 수 있다.

cf) 참고로 스프링의 ProxyFactory라는 것이 CGLIB을 편리하게 사용하게 도와주기 때문에 대략적인 개념만 잡으면 된다.

JDK 동적 프록시에서 실행 로직을 위해 InvocationHandler를 제공했듯이, CGLIB는 MethodInterceptor를 제공한다.

public interface MethodInterceptor extends Callback {
    Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable;
}

예제 코드

public interface ServiceInterface {

    void save();

    void find();
}

@Slf4j
public class ServiceImpl implements ServiceInterface{
    @Override
    public void save() {
        log.info("save 호출");
    }

    @Override
    public void find() {
        log.info("find 호출");
    }
}

@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 실행");
        final long startTime = System.currentTimeMillis();

        final Object result = methodProxy.invoke(target, args);
        
        final long endTime = System.currentTimeMillis();
        final long resultTime = endTime - startTime;
        log.info("TimeProxy 종료 resultTime = {}", resultTime);

        return result;
    }
}

TimeMethodInterceptor는 MethodInterceptor 인터페이스를 구현해서 CGLIB 프록시의 실행 로직을 정의한다.
JDK 동적 프록시를 설명할 때 예제와 거의 같은 코드다.
Object target : 프록시가 호출할 실제 대상
proxy.invoke(target, args) : 실제 대상을 동적으로 호출한다.
참고로 method를 사용해도 되지만 CGLIB는 성능상 MethodProxy proxy를 사용하는 것을 권장한다.

@Slf4j
public class CglibTest {

    @Test
    void cglibTest() {
        final ConcreteService target = new ConcreteService();

        final Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(ConcreteService.class);
        enhancer.setCallback(new TimeMethodInterceptor(target));
        final ConcreteService proxy = (ConcreteService) enhancer.create();

        log.info("targetClass = {}", target.getClass());
        log.info("proxyClass = {}", proxy.getClass());
        
        proxy.call();
    }
}

실행 결과

targetClass = class hello.proxy.common.service.ConcreteService
proxyClass = class hello.proxy.common.service.ConcreteService $$ EnhancerByCGLIB$$25d6b0e3
TimeProxy 실행
ConcreteService 호출
TimeProxy 종료 resultTime = 24

Enhancer : CGLIB은 Enhancer를 사용해서 프록시를 생성한다.
enhancer.setSuperclass(ConcreteService.class) : CGLIB는 구체 클래스를 상속 받아서 프록시를 생성할 수 있다. 어떤 구체 클래스를 상속 받을지 지정한다.
enhancer.setCallback(new TimeMethodInterceptor(target)) : 프록시에 적용할 실행 로직을 할당한다.
enhancer.create() : 프록시를 생성한다. enhancer.setSuperclass(ConcreteService.class)에서 지정한 클래스를 상속 받아서 프록시가 만들어진다.

JDK 동적 프록시는 인터페이스를 구현(implements)해서 프록시를 만든다.
CGLIB은 구체 클래스를 상속(extends)해서 프록시를 만든다.

정리

CGLIB 제약

  • 클래스 기반 프록시는 상속을 사용하기 때문에 몇가지 제약이 있다.
    • 부모 클래스의 생성자를 체크해야 한다. -> CGLIB은 자식 클래스를 동적으로 생성하기 때문에 기본 생성자가 필요하다.
    • 클래스에 final 키워드가 붙으면 상속이 불가능 하다. -> CGLIB에서는 예외가 발생한다.
    • 메소드에 final 키워드가 붙으면 해당 메소드를 오버라이딩 할 수 없다. -> CGLIB에서는 프록시 로직이 동작하지 않는다.
profile
현시깁니다

0개의 댓글