지난 포스팅에서 JDK 동적 프록시를 활용하여 프록시를 여러개 생성할 필요 없이 하나의 프록시 클래스를 생성하여 여러 타겟에 프록시를 적용할 수 있게 되었다.
하지만 JDK 동적 프록시의 경우 인터페이스를 기반으로 생성되기 때문에 인터페이스를 사용하지 않는 프로젝트에서는 사용할 수 없다는 문제가 있었다.
그렇다면 이 문제는 어떻게 해결해야 할까?
Code Generator Library
코드로 확인해보자.
@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 proxy) throws Throwable {
log.info("TimeProxy 실행");
long startTime = System.currentTimeMillis();
// CGLIB 성능상 proxy 사용하는게 속도가 더 빠름
Object result = proxy.invoke(target, args);
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("TimeProxy 종료 resultTime={}",resultTime);
return result;
}
}
이제 테스트 코드로 CGLIB을 활용해보자.
@Test
void cglib() {
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();
}
테스트를 확인해보면
프록시로 CGLIB이 만든 객체가 들어가 있는 것과,
프록시에 정의한 부가 기능이 수행되고 있는 것을 확인할 수 있다.
객체 의존 관계는 다음과 같다
이러한 문제로 인해 실제 프로젝트에 CGLIB을 사용하는 것에 문제가 발생하는데, 보통 컨트롤러, 서비스, 레포지토리 객체는 의존성 주입을 위해 파라미터가 있는 생성자를 사용한다. 자바에서는 아무런 생성자가 없으면 기본적으로 파라미터를 받지 않는 기본 생성자를 만들어주지만, 생성자 메서드가 하나라도 있을 경우 기본 생성자를 만들어주지 않기 때문이다.
따라서 실제 프로젝트에서 CGLIB을 적용하기 위해서는 기본 생성자를 정의하고, setter를 이용해 의존 관계를 주입해줘야 한다.
이번 포스팅에서는 CGLIB에 대해서 알아보았다.
CGLIB은 상속을 이용하기 때문에 구현 클래스만 있는 경우에도 동적 프록시를 생성할 수 있게 되었다.
그런데 어떤 것은 인터페이스가 있고, 어떤 것을 구현 클래스만 있는 경우에는 어떻게 해야할까?
두 경우 모두 같은 부가 기능을 필요로 한다면 InvocationHandler와 MethodInterceptor를 각각 정의해야 할까?
이처럼 CGLIB에서 드러난 제약과 각 상황에서 직면할 수 있는 이런 문제들을 해결할 수 있는게 ProxyFactory이다.
다음 포스팅에서는 ProxyFactory에 대해 알아보자.