JDK Dynamic Proxy

이창근·2023년 8월 17일
0

Spring공부

목록 보기
3/9

프록시를 사용하면 기존 코드를 변경하지 않고 부가 기능을 추가할 수 있지만, 사용하는 클래스 수 만큼 프록시 클래스를 생성해야 한다.

이번에는 이를 JDK 동적 프록시 기술을 사용해서 줄이고자 한다.

InvocationHandler.interface

public interface InvocationHandler {
 public Object invoke(Object proxy, Method method, Object[] args)
 throws Throwable;
}

JDK 동적 프록시를 적용할 로직은 위 인터페이스를 구현하면 된다. 우리가 만들었던 로깅 프록시를 이처럼 구현해보자.

TimeInvocationHandler.class

@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();
  1. 위 함수를 통해 proxy를 동적으로 생성해주고 이를 call한다.
  2. proxy는 InvocationHandler.invoke()를 호출하고, 우리는 TimeInvocationHandler를 그 구현체로 뒀으므로 TimeInvocationHandler.invoke()가 실행된다.
  3. TimeInvocationHandler의 내부 로직이 수행된다.
  4. method.invoke(target, args)가 수행되면, target으로 두었던 실제 객체를 호출한다.
  5. 실제 객체의 call()이 실행된다.
  6. 실제 객체의 call()이 끝나면 TimeInvocationHandler에서 나머지 로직이 실행되고 로그가 출력된다.

이런 구조를 통해 우리는 직접 proxy클래스를 만드는 것이 아니라, TimeInvocationHandler를 통해 만들 수 있다. 이를 통해 대상 클래스 개수 만큼 프록시 클래스를 만들 필요가 없어진다.

이를 우리 클래스 적용시켜 본다면, 수동 빈 등록을 사용하여 아래처럼 만들어볼 수 있었다.

DynamicProxyBasicConfig.class

@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에 대해 알아보겠다.

profile
나중에 또 모를 것들 모음

1개의 댓글

comment-user-thumbnail
2023년 8월 17일

글 잘 봤습니다.

답글 달기