인터페이스라면 JDK 동적 프록시, 구체 클래스일 경우에는 CGLIB를 사용해야하는 상황에서
동적 프록시를 쉽게 사용하기 위해 스프링이 제공하는 동적 프록시 기술로 기본적으로 클라이언트가 프록시를 요청하면 인터페이스인지 구체 클래스인지 판단해 프록시를 생성해준다.
JDK 동적 프록시같은 경우에는 InvokeHandler가 필요하고 CGLIB는 MethodInterceptor가 필요하다. 스프링은 이 둘을 개념적으로 추상화시켜 Advice라는 것을 만들어냈고 프록시 팩토리에서는 Advice를 사용하면 된다. 스프링 부트는 기본적으로 CGLIB만을 사용한다.
// cglib의 MethodInterceptor가 아닌 스프링 aop의 것이며 Advice를 상속받기 때문에 Advice라 한다
@Slf4j
public class TimeAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
log.info("TimeProxy 실행");
long startTime = System.currentTimeMillis();
Object result = invocation.proceed();
long endTimeTime = System.currentTimeMillis();
long resultTime = endTimeTime - startTime;
log.info("TimeProxy 종료 resultTime = {}", resultTime);
return result;
}
}
//ProxyFactory가 생성할 때 target을 파라미터로 받는데 setObject()로 타겟정보를 들고있기 때문에
//위의 Advice 콜백함수에서 기존 jdk 동적 프록시와 cglib처럼
//Method라던지 target을 이용해서 직접 조작하지 않아도 proceed()를 이용해 쉽게 사용할 수 있다.
@Test
@DisplayName("인터페이스가 있으면 JDK 동적 프록시 사용")
public void interfaceProxy () throws Exception{
ServiceInterface target = new ServiceImpl();
ProxyFactory proxyFactory = new ProxyFactory(target);
//편의성으로 addAdvice를 해도 내부적으로 DefaultPointcutAdvisor가 들어간다.
proxyFactory.addAdvice(new TimeAdvice());
ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();
log.info("targetClass={}", target.getClass());
log.info("proxyClass={}", proxy.getClass());
proxy.save();
assertThat(AopUtils.isAopProxy(proxy)).isTrue();
assertThat(AopUtils.isJdkDynamicProxy(proxy)).isTrue();
assertThat(AopUtils.isCglibProxy(proxy)).isFalse();
}
14:45:25.133 [main] INFO ProxyFactoryTest - targetClass=ServiceImpl
14:45:25.139 [main] INFO ProxyFactoryTest - proxyClass=class com.sun.proxy.$Proxy10
14:45:25.158 [main] INFO TimeAdvice - TimeProxy 실행
14:45:25.159 [main] INFO ServiceImpl - save 호출
14:45:25.160 [main] INFO TimeAdvice - TimeProxy 종료 resultTime = 0