반응형
2022.09.27 - [프로그래밍 노트/SPRING] - [Spring] 동적 프록시 기술(feat. 리플렉션)
바로 직전 포스팅에서 동적 프록시 기술에 대해 알아보았는데, 이런 의문점을 가질 수 있다.
- 인터페이스가 있는 클래스는 Jdk Dynmic Proxy를 사용하고, 존재하지 않는 경우에는 CGLib 를 사용해야하니 InvocationHandler 와 MethodInterceptor 를 모두 구현해놔야하는 것인가?
물론.. 그래도 되지만 스프링에서는 프록시 생성을 추상화하여 프록시 팩토리(ProxyFactory)라는 것을 제공해준다.
우리는 타겟 객체가 인터페이스를 구현했는지 안했는지 알필요가 없다.
프록시 팩토리에서 인터페이스가 있으면 자동으로 Jdk Dynamic Proxy를 사용하고, 구체 클래스만 있다면 CGLib를 사용하기 때문이다.
여기서 Advice
라는 개념이 등장한다. Advice는 Proxy에서 호출하는 부가기능이라고 생각하면 된다. 개발자가 InvocationHandler 또는 MethodInterceptor에 구현했던 내용이라고 생각하면 더 이해가 쉽게 되겠다.
Advice
라는 개념을 도입함으로써 개발자는 InvocationHandler 나 MethodInterceptor를 신경쓰지 않아도 된다. 내부적으로 Advice 호출용 InvocationHandler, MethodInterceptor가 Advice 를 호출하기 때문이다.
프록시 팩토리(Proxy Factory)
MethodInterceptor 인터페이스를 구현하여 Advice
를 만들자. 이전 포스팅에서 만든 것과 같이 메소드 실행시간을 반환하는 부가기능이다.
TimeAdvice.class
@Slf4j
public class TimeAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// MethodInvocation 에는 현재 프록시 객체 인스턴스, args, 메서드 정보 등이 존재한다.
// Handler 에서 파라미터로 존재했던 것들이 MethodInvocation에 담겼다고 생각하면 된다.
log.info("TimeProxy 실행");
long startTime = System.currentTimeMillis();
// Porxy를 생성하는 단계에서 target 정보를 받기 때문에 target 정보는 이미 MethodInvocation 안에 포함되어 있다.
Object result = invocation.proceed();
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("TimeProxy 종료 resultTime={}", resultTime);
return result;
}
}
@Test
@DisplayName("인터페이스가 있으면 JDK 동적 프록시 사용")
void interfaceProxy() {
Animal target = new Cat();
ProxyFactory proxyFactory = new ProxyFactory(target);
proxyFactory.addAdvice(new TimeAdvice());
Animal proxy = (Animal) proxyFactory.getProxy();
log.info("targetClass={}, proxyClass={}", target.getClass(), proxy.getClass());
proxy.bark();
assertThat(AopUtils.isAopProxy(proxy)).isTrue();
assertThat(AopUtils.isJdkDynamicProxy(proxy)).isTrue();
assertThat(AopUtils.isCglibProxy(proxy)).isFalse();
}
// targetClass=class hello.proxy.jdkdynamic.code.Cat
// proxyClass=class com.sun.proxy.$Proxy13
@Test
@DisplayName("구체 클래스만 있으면 CGLIB 사용")
void concreteProxy() {
Dog target = new Dog();
ProxyFactory proxyFactory = new ProxyFactory(target);
proxyFactory.addAdvice(new TimeAdvice());
Dog proxy = (Dog) proxyFactory.getProxy();
log.info("targetClass={}, proxyClass={}", target.getClass(), proxy.getClass());
proxy.bark();
assertThat(AopUtils.isAopProxy(proxy)).isTrue();
assertThat(AopUtils.isJdkDynamicProxy(proxy)).isFalse();
assertThat(AopUtils.isCglibProxy(proxy)).isTrue();
}
// targetClass=class hello.proxy.proxyfactory.ProxyFactoryTest$Dog,
// proxyClass=class hello.proxy.proxyfactory.ProxyFactoryTest$Dog$$EnhancerBySpringCGLIB$$71e6d652
static class Dog {
public String bark() {
return "왈왈";
}
}
- 프록시 팩토리의 서비스 추상화 덕분에 구체적인 CGLib, Jdk Dynamic Proxy 기술에 의존하지 않고 매우 편리하게 동적 프록시를 생성할 수 있다.
- 부가 기능 로직도 특정 기술에 종속적이지 않게
Advice
하나로 편리하게 사용할 수 있다.
내용) 스프링 핵심 원리 - 고급편 (김영한님)
반응형
'프로그래밍 노트 > SPRING' 카테고리의 다른 글
[Spring] 빈 후처리기 (feat. proxy, advisor) (0) | 2022.10.11 |
---|---|
[Spring] 프록시팩토리_2 (feat. Advisor) (0) | 2022.09.30 |
[Spring] 동적 프록시 기술(feat. 리플렉션) (0) | 2022.09.27 |
[Spring] 프록시 활용 - 프록시 패턴, 데코레이터 패턴 (0) | 2022.09.26 |
[Spring] 스프링의 트랜잭션 기술 (0) | 2022.04.16 |