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 |