ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 프록시 팩토리(ProxyFactory)
    스프링/스프링 AOP 2023. 9. 17. 16:10

    스프링에서 지원하는 프록시 팩토리란 동적 프록시를 통합해서 편리하게 만들어주는 기능이다.

    인터페이스가 있으면 JDK 동적 프록시를 사용하고, 구체 클래스만 있다면 CGLIB를 사용한다.

     

    JDK 동적 프록시나 CGLIB를 수동으로 생성하는 것이 아니라 프록시 팩토리가 프록시를 자동으로 만들어주는 것이다.

    (JDK 동적 프록시와 CGLIB는 직접적으로 사용하지 않는다 해서 포스팅을 안했는데 나중에 따로 찾아봐야 한다)

    - 간단하게 인터페이스면 JDK 동적 프록시, 구체 클래스면 CGLIB

     

    프록시 팩토리는 기본적으로 advice를 통해 이루어진다.

     

     

     

    TimeAdvice.class

    @Slf4j
    public class TimeAdvice implements MethodInterceptor {
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            log.info("TimeProxy 실행");
            long startTime = System.currentTimeMillis();
    
            // Object result = method.invoke(target, args);
            Object result = invocation.proceed();
    
            long endTime = System.currentTimeMillis();
            long resultTime = endTime - startTime;
            log.info("TimeProxy 종료 resultTime={}", resultTime);
            return result;
        }
    }

    프록시인데 target을 호출하지도, 사용하지도 않는다. 그 이유는 target 클래스의 정보가 MethodInvocation invacation 이라는 파라미터 안에 이미 들어있기 때문이다. 스프링에서 지원하는 프록시 팩토리는 프록시를 생성하는 단계에서 이미 target의 정보를 파라미터로 전달받기 때문이다. MethodInterceptor는 org.aopalliance.intercept의 경로에 있는 MethodInterceptor를 import 해야한다.

     

     

     

     

    1. 인터페이스가 있을 경우

    @Test
    @DisplayName("인터페이스가 있으면 JDK 동적 프록시 사용")
    void interfaceProxy() {
        ServiceInterface target = new ServiceImpl();
        ProxyFactory proxyFactory = new ProxyFactory(target);
        // 여기서 이미 타겟 정보를 알고있음
        proxyFactory.addAdvice(new TimeAdvice());
        ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();
        log.info("targetClass={}", target.getClass());
        log.info("proxyClass={}", proxy.getClass());
    
        proxy.save();
    }

    인터페이스가 있을 경우에는 JDK 동적 프록시를 사용한다.

    위에서 TimeAdvice를 만들 때 target을 선언하지 않았는데, 여기서 넘겨주기에 이미 알고있기 때문이다.

    테스트를 실행하면 프록시가 생성되고, save가 호출되는 시점에서 프록시가 실행되어서 실행시간을 알려준다.

     

     

     

     

    2. 구체 클래스만 있을 경우

    @Test
    @DisplayName("구체 클래스만 있으면 CGLIB 사용")
    void concreteProxy() {
        ConcreteService target = new ConcreteService();
        ProxyFactory proxyFactory = new ProxyFactory(target);
        // 여기서 이미 타겟 정보를 알고있음
        proxyFactory.addAdvice(new TimeAdvice());
        ConcreteService proxy = (ConcreteService) proxyFactory.getProxy();
        log.info("targetClass={}", target.getClass());
        log.info("proxyClass={}", proxy.getClass());
    
        proxy.call();
    }

    ConcreteService는 인터페이스가 없고 실제 구체 클래스만 있는 클래스이다. 방식은 위와 완전히 동일하다.

    구체 클래스는 CGLIB를 사용한다.

     

     

     

     

    3. 둘 다 있을 때 CGLIB로 사용하는 경우

    @Test
    @DisplayName("ProxyTargetClass 옵션을 사용하면 인터페이스가 있어도 CGLIB를 사용하고, 클래스 기반 프록시 사용")
    void ProxyTargetClass() {
        ServiceInterface target = new ServiceImpl();
        ProxyFactory proxyFactory = new ProxyFactory(target);
        proxyFactory.setProxyTargetClass(true); // CGLIB 기반으로 만든다는 설정
        proxyFactory.addAdvice(new TimeAdvice());
        ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();
        log.info("targetClass={}", target.getClass());
        log.info("proxyClass={}", proxy.getClass());
    
        proxy.save();
    }

    별도의 설정을 통해 인터페이스가 있어도 JDK 동적 프록시가 아니라 CGLIB를 사용하여 클래스 기반 프록시를 사용하도록 설정한 것이다.

    ServiceImpl은 인터페이스가 있음에도 CGLIB를 상속받아서 사용하는 것이다.

     

    proxyTargetClass = true로 설정하면 인터페이스의 여부와 상관없이 무조건 CGLIB인 것이다.

     

     

     

    결론

    프록시 팩토리가 내부에서 동적으로 JDK 동적 프록시, CGLIB를 선택해주기 때문에 프록시를 수동으로 선택하지 않고 편리하게 동적 프록시를 생성할 수 있다.

     

     

    스프링 부트는 AOP를 적용할 때 기본적으로 proxyTargetClass=true로 설정해서 사용한다는데 나중에 이것에 대해 제대로 포스팅 할 것이다.

Designed by Tistory.