프록시 패턴
프록시 패턴은 하나의 객체가 다른 객체의 대리자 역할을 하도록 설계하는 디자인 패턴으로 대리자는 실제 객체와 동일한 인터페이스를 가지며, 클라이언트는 실제 객체를 직접 사용하는 대신 대리자를 통해 간접적으로 상호작용한다.
이 패턴을 사용함으로써 얻는 이점은 다음과 같다 :
1. 로깅 : 프록시 객체는 클라이언트의 요청과 실제 객체의 작업 결과를 로깅할 수 있다.
2. 보안 강화 : 프록시 객체가 클라이언트의 요청을 검증하고 필요한 권한 확인 등의 작업을 수행함으로써 보안상의 이점을 가져간다.
3. 성능 최적화 : 실제 객체가 생성 및 초기화에 많은 비용이 드는 경우, 프록시 객체를 사용하여 실제 객체가 필요한 시점에 생성하거나 초기화할 수 있다.
스프링에서는 프록시 객체를 만드는 여러 가지 방법을 제공하고 있으며 가장 대표적인 방법 몇 가지를 정리하려 한다.
ProxyFactory
자바에서 프록시 객체를 만드는 대표적인 방법으로 JDK 리플렉션과 CGLIB 라이브러리가 사용된다. 조금 더 대중적인 단어로는 JDK 동적 프록시와 CGLIB라고도 하는데 이 둘의 가장 큰 차이는 JDK 동적 프록시는 인터페이스를 기반으로 프록시 객체를 생성하고 CGLIB는 구체 클래스 기반으로 프록시 객체를 생성한다.
스프링은 다양한 케이스를 커버하기 위해 두 기능을 모두 지원하고 이것을 ProxyFactory로 추상화했다.
public class ProxyFactory extends ProxyCreatorSupport {
public ProxyFactory() {
}
public ProxyFactory(Object target) {
setTarget(target);
setInterfaces(ClassUtils.getAllInterfaces(target));
}
```
}
프록시 객체에 적용할 행동을 JDK 동적 프록시는 InvocationHandler에, CGLIB는 MethodInterceptor에 정의했는데 스프링은 이것 또한 MethodInterceptor로 추상화했다. (CGLIB의 MethodInterceptor와 다르다.)
@FunctionalInterface
public interface MethodInterceptor extends Interceptor {
@Nullable
Object invoke(@Nonnull MethodInvocation invocation) throws Throwable;
}
JDK 동적 프록시 예제
프록시를 적용할 객체(target)을 생성하고 ProxyFactory에 target과 행동(advice)을 전달한다.
기본적으로 인터페이스가 존재하면 자동으로 JDK 동적 프록시를 사용해서 인터페이스 기반 프록시 객체를 생성한다.
@Test
@DisplayName("인터페이스가 존재하면 JDK 동적 프록시 사용")
void interfaceProxy() {
ServiceInterface target = new ServiceImpl();
ProxyFactory proxyFactory = new ProxyFactory(target);
proxyFactory.addAdvice(new TimeAdvice());
ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();
assertThat(AopUtils.isAopProxy(proxy)).isTrue();
assertThat(AopUtils.isJdkDynamicProxy(proxy)).isTrue();
assertThat(AopUtils.isCglibProxy(proxy)).isFalse();
}
CGLIB
JDK 동적 프록시와 마찬가지로 target을 생성하고 advice를 전달한다.
인터페이스가 없다면 자동으로 CGLIB를 사용해서 구체 클래스 기반으로 프록시 객체를 생성한다.
@Test
@DisplayName("구체 클래스가 있으면 CGLIB 사용")
void concreteProxy() {
ConcreteService target = new ConcreteService();
ProxyFactory proxyFactory = new ProxyFactory(target);
proxyFactory.addAdvice(new TimeAdvice());
ConcreteService proxy = (ConcreteService) proxyFactory.getProxy();
assertThat(AopUtils.isAopProxy(proxy)).isTrue();
assertThat(AopUtils.isJdkDynamicProxy(proxy)).isFalse();
assertThat(AopUtils.isCglibProxy(proxy)).isTrue();
}
CGLIB를 강제로 사용하기
ProxyFactory가 제공하는 setProxyTargetClass를 활성화하면 인터페이스 유무와 상관없이 CGLIB를 사용해서 프록시 객체를 생성한다.
@Test
@DisplayName("proxyTargetClass 옵션을 사용하면 인터페이스가 있어도 CGLIB을 사용하고 클래스 기반 프록시 사용")
void proxyTargetClass() {
ServiceInterface target = new ServiceImpl();
ProxyFactory proxyFactory = new ProxyFactory(target);
// ServiceImpl 기반으로 상속받아서 프록시 생성
proxyFactory.setProxyTargetClass(true);
proxyFactory.addAdvice(new TimeAdvice());
ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();
assertThat(AopUtils.isAopProxy(proxy)).isTrue();
assertThat(AopUtils.isJdkDynamicProxy(proxy)).isFalse();
assertThat(AopUtils.isCglibProxy(proxy)).isTrue();
}
'Spring > Spring' 카테고리의 다른 글
[Spring] Bean을 stateless하게 설계해야하는 이유 (1) | 2023.12.03 |
---|---|
[Spring] Interface로 간단한 Listener 구현하기 (0) | 2022.11.28 |
[Spring] Argument Resolver란 (HandlerMethodArgumentResolver, WebMvcConfigurer) (0) | 2022.10.11 |
[Spring] Interceptor란 (HandlerInterceptor, WebMvcConfigurer) (0) | 2022.10.03 |