Spring

[Spring] CGLIB: Code Generator Library

lakelight 2022. 8. 24. 11:34
728x90
반응형

CGLIB

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

 

이미지 출처: 인프런 김영한 강사님 강의자료

 

CGLIB 구현

NonInteferfaceService
인터페이스가 없는 구현체 서비스를 만들었습니다.
@Slf4j
public class NonInterfaceService {
    public void call() {
        log.info("NonInterfaceService call");
    }
}

 

ExecuteTimeMethodInterceptor
CGLIB을 사용하기 위해 MethodInterceptor를 상속받아 CGLIB 프록시의 실행 로직을 정의합니다.
@Slf4j
public class ExecuteTimeMethodInterceptor implements MethodInterceptor {
    /**
     * CGLIB 프록시의 실행 로직을 정의합니다.
     */

    private final Object target;

    public ExecuteTimeMethodInterceptor(Object target) {
        this.target = target;
    }

    @Override
    public Object intercept(Object obj/*프록시가 호출할 실제 대상*/, 
                        Method method, Object[] args, 
                        MethodProxy methodProxy) throws Throwable {
        log.info("ExecuteTimeProxy Execute");
        long startTime = System.currentTimeMillis();

        //methodProxy를 사용하면 method를 사용하는 것보다 성능이 좋다고 합니다.
        Object result = methodProxy.invoke(target, args);

        long endTime = System.currentTimeMillis();
        long executeTime = endTime - startTime;
        log.info("ExecuteTimeProxy Terminate executeTime={}", executeTime);

        return result;
    }
}

 

CglibEX
실제로 CGLIB을 이용해서 프록시 객체를 만들고 로그를 찍어보는 테스트
@Slf4j
public class CglibEX {

    @Test
    void cglibEX() {
        // 인터페이스가 없는 클래스도 프록시 객체를 생성할 수 있는지 테스트합니다.
        NonInterfaceService target = new NonInterfaceService();

        // CGLIB를 생성하는 코드입니다.
        Enhancer enhancer = new Enhancer();

        // CGLIB를 이용해 동적 프록시를 만들어야하는데 인터페이스가 없기 때문에
        // 구체 클래스를 기반으로 NonInterfaceService를 상속받은 프록시를 만들어야 합니다.
        // 그래서 NonInterfaceService 클래스를 지정해줍니다.
        enhancer.setSuperclass(NonInterfaceService.class);

        // 프록시가 실행할 로직을 지정해주는 코드입니다.
        enhancer.setCallback(new ExecuteTimeMethodInterceptor(target));

        // CGLIB이 생성한 프록시 객체가 반환됩니다.
        NonInterfaceService proxy = (NonInterfaceService) enhancer.create();

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

        proxy.call();

    }
}

 

출력 결과

11:21:58.943 [Test worker] INFO hello.proxy.cglib.CglibEX - targetClass=class hello.proxy.common.service.NonInterfaceService
11:21:58.948 [Test worker] INFO hello.proxy.cglib.CglibEX - proxyClass=class hello.proxy.common.service.NonInterfaceService$$EnhancerByCGLIB$$4c43754e
11:21:58.948 [Test worker] INFO hello.proxy.cglib.code.ExecuteTimeMethodInterceptor - ExecuteTimeProxy Execute
11:21:58.961 [Test worker] INFO hello.proxy.common.service.NonInterfaceService - NonInterfaceService call
11:21:58.962 [Test worker] INFO hello.proxy.cglib.code.ExecuteTimeMethodInterceptor - ExecuteTimeProxy Terminate executeTime=14

 

결론

CGLIB이 어떤방식으로 동작하는지 알아보았습니다.
CGLIB이 스프링에서 많이 사용되는 것으로 알고있는데
앞으로는 CGLIB이 생성한 프록시 객체를 보게되면 반가울 것 같습니다.

포스팅 끝까지 봐주셔서 감사합니다.

 

[참고]

1. 인프런 스프링 핵심 원리 - 김영한

728x90
반응형