본문 바로가기
Spring

CGLIB

by sangyunpark99 2025. 3. 10.
JDK의 동적 프록시는 인터페이스를 기반으로 프록시 객체를 생성하는데,
그렇다면 클래스의 프록시 객체는 어떻게 생성할 수 있을까?

 

이번글은 클래스의 프록시 객체를 생성해주는 CGLIB에 대해 정리했습니다.

참고 강의 : 김영한의 스프링 핵심 원리 - 고급편

 

 

CGLIB

Code Generator Library로 바이트 코드를 조작해서 동적으로 클래스를 생성하는 라이브러리 입니다.

인터페이스를 사용하지 않아도 구체 클래스로 동적 프록시를 만들 수 있습니다.

 

CGLIB 적용

간단한 예제로 CGLIB가 어떻게 사용되는지 알아보겠습니다.

먼저, 구체 클래스만 있는 서비스 클래스를 생성합니다.

 

ConcreteService

package hello.proxy.common.service;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ConcreteService {
    public void call() {
        log.info("Concrete Service 호출");
    }
}

 

 

CGLIB는 MethodInterceptor를 사용해서 동적 프록시 객체를 생성해줍니다.

 

MethodInterceptor

package org.springframework.cglib.proxy;

import java.lang.reflect.Method;

public interface MethodInterceptor extends Callback {
    Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}

 

Object var1 : CGLIB가 적용된 객체를 의미합니다.

Method var2 : 호출된 메서드를 의미합니다.

Object[] var3 : 메서드를 호출하면서 전달된 인수를 의미합니다.

MethodProxy var4 : 메서드 호출에 사용됩니다.

 

 

메서드의 실행 시간을 출력해주는 MethodInterceptor를 구현한 TimeMethodIntercetpor 클래스는 다음과 같습니다.

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);

        long endTime = System.currentTimeMillis();

        log.info("TimeProxy 종료 resultTime={}", endTime - startTime);

        return result;
    }
}

 

method.invoke()를 사용해도 되지만, methodProxy.invoke()를 사용하는 것이 더 권장됩니다.

 

테스트 코드로 실제로 동적으로 프록시 객체가 할당되는지 확인해보겠습니다.

    @Test
    void cglib() throws Exception {

        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();
    }

 

Enhancer : CGLIB기 프록시를 생성하는데 사용됩니다.

enhancer.setSuperclass(ConcreteService.class) : CGLIB가 어떤 구체 클래스를 상속 받을지 지정합니다.

enhancer.setCallback(new TimeMethodIntercetpor(target)) : 프록시에 적용할 실행 로직을 할당합니다.

enhancer.create() : 프록시를 생성합니다.

 

CGLIB는 구체 클래스를 상속해서 프록시 객체를 만듭니다.

 

출력 결과

 

CGLIB로 생성된 클래스 이름은 "ConcreteService$$EnhancerByCGLIB$$25d6b0e3"가 됩니다.

형식은 "대상클래스$$EnhancerByCGLIB$$임의코드" 입니다.

 

CGLIB 의존관계

 

클래스 의존관계

 

런타임 객체 의존관계

 

CGLIB는 클래스 상속을 통해서 프록시 객체를 생성하므로 부모 클래스의 기본 생성자가 반드시 필요합니다.

또한, 클래스에 final 키워드를 붙이면 안 되고, 메서드에 final 키워드를 붙이면 오버라이딩이 불가능하므로 사용하면 안 됩니다.

 

 

정리

 

  • CGLIB는 클래스 상속을 이용해 프록시 객체를 생성하므로, 부모 클래스의 기본 생성자가 반드시 필요합니다.
  • 인터페이스 없이도 구체 클래스를 기반으로 동적 프록시를 생성할 수 있으며, MethodInterceptor를 통해 실행 로직을 제어합니다.
  • 클래스에 final 키워드를 붙이면 상속이 불가능하고, 메서드에 final이 붙으면 오버라이딩이 불가능하여 CGLIB 프록시가 적용되지 않습니다.

 

'Spring' 카테고리의 다른 글

어드바이저  (0) 2025.03.12
프록시 팩토리  (0) 2025.03.11
동적 프록시  (0) 2025.03.08
트랜잭션 AOP 주의사항  (0) 2025.03.01
Thread Local  (0) 2025.02.28