프록시를 사용하면 기존 코드를 변경하지 않고 부가 기능을 추가할 수 있지만, 사용하는 클래스 수 만큼 프록시 클래스를 생성해야 한다.
이번에는 이를 JDK 동적 프록시 기술을 사용해서 줄이고자 한다.
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
JDK 동적 프록시를 적용할 로직은 위 인터페이스를 구현하면 된다. 우리가 만들었던 로깅 프록시를 이처럼 구현해보자.
@Slf4j
public class TimeInvocationHandler implements InvocationHandler {
private final Object target;
public TimeInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("TimeProxy 실행");
long startTime = System.currentTimeMillis();
Object result = method.invoke(target, args);
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("TimeProxy 종료, resultTime={}", resultTime);
return result;
}
}
@Test
void jdkProxy() {
classAInterface target = new AImpl();
TimeInvocationHandler handler = new TimeInvocationHandler(target);
//넣어준 인터페이스 기반으로 handler를 실행한다.
AInterface proxy = (AInterface) Proxy.newProxyInstance(
AInterface.class.getClassLoader(),
new Class[]{AInterface.class},
handler
);
proxy.call();
log.info("targetClass={}", target.getClass());
log.info("proxyClass={}", proxy.getClass());
}
보이듯이 사용법이 조금 어려운데, 순서대로 설명하자면,
Proxy.newProxyInstance();
이런 구조를 통해 우리는 직접 proxy클래스를 만드는 것이 아니라, TimeInvocationHandler를 통해 만들 수 있다. 이를 통해 대상 클래스 개수 만큼 프록시 클래스를 만들 필요가 없어진다.
이를 우리 클래스 적용시켜 본다면, 수동 빈 등록을 사용하여 아래처럼 만들어볼 수 있었다.
@Configuration
public class DynamicProxyBasicConfig {
@Bean
public OrderControllerV1 orderControllerV1(LogTrace logTrace) {
OrderControllerV1 orderControllerV1 = new OrderControllerV1Impl(orderServiceV1(logTrace));
OrderControllerV1 proxy = (OrderControllerV1) Proxy.newProxyInstance(
OrderControllerV1.class.getClassLoader(),
new Class[]{OrderControllerV1.class},
new LogTraceBasicHandler(orderControllerV1, logTrace));
return proxy;
}
@Bean
public OrderServiceV1 orderServiceV1(LogTrace logTrace) {
OrderServiceV1 orderServiceV1 = new OrderServiceV1Impl(orderRepositoryV1(logTrace));
OrderServiceV1 proxy = (OrderServiceV1) Proxy.newProxyInstance(
OrderServiceV1.class.getClassLoader(),
new Class[]{OrderServiceV1.class},
new LogTraceBasicHandler(orderServiceV1, logTrace));
return proxy;
}
@Bean
public OrderRepositoryV1 orderRepositoryV1(LogTrace logTrace) {
OrderRepositoryV1 orderRepositoryV1 = new OrderRepositoryV1Impl();
OrderRepositoryV1 proxy = (OrderRepositoryV1) Proxy.newProxyInstance(
OrderRepositoryV1.class.getClassLoader(),
new Class[]{OrderRepositoryV1.class},
new LogTraceBasicHandler(orderRepositoryV1, logTrace)
);
return proxy;
}
}
JDK 동적 프록시 기술은 동적으로 프록시를 생성하여 프록시 클래스를 직접 만들 필요가 없는 것에 장점이 있다.
하지만 큰 단점이 하나 있는데, 동적 프록시 생성에 인터페이스가 꼭 필요하다는 것이다. 크게 어려운 작업이 아닐 수 있지만 만약 내가 이미 구체 클래스들만으로 코드를 작성하고 있었다면 여간 귀찮은 일이 아닐 수 없다. 다음에는 구체 클래스만으로 동적 프록시 생성을 할 수 있는 CGLIB에 대해 알아보겠다.
글 잘 봤습니다.