본 게시물은 스스로의 공부를 위한 글입니다.
잘못된 내용이 있으면 댓글로 알려주세요!
JDK 동적 프록시는 여기를 참고 바란다.
@Slf4j
public class ConcreteService {
public void call(){
log.info("ConcreteService 호출");
}
}
MethodInterceptor
을 구현하면 된다.@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 methodProxy) throws Throwable {
log.info("TimeProxy 실행");
long startTime = System.currentTimeMillis();
Object result = methodProxy.invoke(target, args);
long endTime = System.currentTimeMillis();
long resultTime = endTime-startTime;
log.info("TimeProxy 종료 resultTime={}", resultTime);
return result;
}
}
Obejct intercept(Obejct obj, Method method, Object[] args, MethodProxy methodProxy)
obj
: CGLIB가 적용된 객체method
: 호출된 메서드args
: 메서드를 호출하면서 전달된 인수proxy
: 메서드 호출에 사용methodProxy.invoke(target, args)
: 호출할 객체와 인자를 넘겨서 실행시킨다.🎈 실행
void cglib() {
ConcreteService target = new ConcreteService();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ConcreteService.class);
enhancer.setCallback(new TimeMethodInterceptor(target));
ConcreteService proxy = (ConcreteService) enhancer.create();
proxy.call();
}
//실행 결과
TimeMethodInterceptor - TimeProxy 실행
ConcreteService - ConcreteService 호출
TimeMethodInterceptor - TimeProxy 종료 resultTime=9
Enhancer
: CGLIB는 Enhancer
을 사용해서 프록시를 생성한다..setSuperClass
: 구체 클래스 지정(상속 이용).setCallback
: 프록시에 적용할 실행 로직 할당.create()
: 프록시 생성final
키워드가 붙으면 상속이 불가능하다.final
키워드가 붙으면 해당 메서드를 오버라이딩 할 수 없다.인터페이스가 있는 경우에는 JDK 동적 프록시를 사용하고, 그렇지 않는 경우에는 CGLIB를 적용하려면 어떻게 해야할까? -> ProxyFactory를 사용하자!
Q) JDK 동적 프록시의 부가 기능을 위해선 InvocationHandler, CGLIB의 부가 기능을 위해선 MethodInterceptor가 필요했었는데.. 그럼 각각 중복으로 따로 만들어야 할까?
A) 아니! 스프링에서는 Advice라는 개념을 도입해 개발자는 이것만 구현하면 된다. 그럼 팩토리가 알아서 적용시켜준다.
🎈 Advice 만들기
InvocationHandler
나 MethodInterceptor
대신에 개발자는 Advice
만 구현하면 된다.Advice
를 만드는 방법은 여러가지가 있지만, 기본적인 방법은 MethodInterceptor
인터페이스를 구현하면 된다.@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 endTime = System.currentTimeMillis();
long resultTime = endTime-startTime;
log.info("TimeProxy 종료 resultTime={}", resultTime);
return result;
}
}
invocation.proceed()
: invocation안에 target 등의 모든 정보가 들어있다. 따라서 .proceed()
만 하면 메서드가 실행한다.🎈 사용
@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();
assertThat(AopUtils.isAopProxy(proxy)).isTrue();
assertThat(AopUtils.isJdkDynamicProxy(proxy)).isTrue();
assertThat(AopUtils.isCglibProxy(proxy)).isFalse();
}
@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();
assertThat(AopUtils.isAopProxy(proxy)).isTrue();
assertThat(AopUtils.isJdkDynamicProxy(proxy)).isFalse();
assertThat(AopUtils.isCglibProxy(proxy)).isTrue();
}
@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();
assertThat(AopUtils.isAopProxy(proxy)).isTrue();
assertThat(AopUtils.isJdkDynamicProxy(proxy)).isFalse();
assertThat(AopUtils.isCglibProxy(proxy)).isTrue();
}
proxyTargetClass(true)
를 사용하면 인터페이스가 있다 하더라도 항상 CGLIB로 동적 프록시를 만든다.인프런의 '스프링 핵심 원리 고급편(김영한)'을 스스로 정리한 글입니다.
자세한 내용은 해당 강의를 참고해주세요.