프록시 패턴(Proxy Pattern)은 원본(Target) 객체 대신 로직의 흐름을 제어할 수 있는 패턴을 말한다.
Client가 원본 객체를 호출할 때, 직접 대상을 호출하는 것이 아니라 중간에 Proxy 객체를 거쳐서 실행되는 것을 의미한다.
OCP
: 기존(Target) 코드의 변경 없이 새로운 기능을 추가할 수 있다.SRP
: 기존 코드가 해야 하는 일을 유지할 수 있다.유연성 향상
: 객체 간의 결합도를 줄일 수 있다.성능 저하
: 객체 생성을 한 단계 더 거치므로 빈번한 객체 생성이 필요한 경우에는 성능저하가 발생할 수 있다.가독성
: Client -> Proxy -> Target 과 같은 구조로 진행이 되어 파악하기가 어려울 수 있다.Proxy
: Proxy용 Interfacepublic interface Proxy {
void execute();
}
TargetClass
: 원본 Target 객체public class TargetClass implements Proxy {
@Override
public void execute() {
System.out.println("원본 객체 실행");
}
}
ProxyClass
: 프록시 객체public class ProxyClass implements Proxy {
private Proxy target;
public ProxyClass(Proxy target) {
this.target = target;
}
@Override
public void execute() {
System.out.println("프록시 객체 실행");
this.target.execute();
System.out.println("프록시 객체 종료");
}
}
실행
public static void main(String[] args) {
Proxy target = new TargetClass(); // 원본객체
Proxy proxy = new ProxyClass(target); // 프록시 객체
proxy.execute(); // 실행
}
-- 작업결과 --
프록시 객체 실행
원본 객체 실행
프록시 객체 종료
- 프록시 패턴을 사용하면 대상 클래스의 수만큼 프록시 클래스를 만들어야 한다는 단점이 존재.
- 동적 프록시 기법으로는 JDK Dynamic Proxy / CGLIB와 같은 기술이 존재.
- Runtime에 동적으로 프록시 생성
- 자바의 reflection을 통해 동적 프록시 기법을 이용하여 해결 가능.
- 리플렉션을 사용하기 때문에 성능상 이슈 존재.
- 인터페이스 기반.
InvocationHandler
인터페이스를 구현해서 작성.public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
Object
: 프록시 객체
Method
: 호출한 메서드
Object[]
: 인수
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
를 이용하여 생성 가능.@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
Objects.requireNonNull(h);
final Class<?> caller = System.getSecurityManager() == null
? null
: Reflection.getCallerClass();
/*
* Look up or generate the designated proxy class and its constructor.
*/
Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
return newProxyInstance(caller, cons, h);
}
ClassLoader
: 클래스 로더 지정
Class<?>[]
: 프록시 객체가 구현해야 할 인터페이스를 나타내는 배열
InvocationHandler
: Proxy Handler
JdkDynamicProxy
: Proxy용 Interfacepublic interface JdkDynamicProxy {
void execute();
}
TargetClass
: 원본 Target 객체public class TargetClass implements JdkDynamicProxy {
@Override
public void execute() {
System.out.println("원본 객체 실행");
}
}
ProxyClass
: 프록시 객체public class ProxyClass implements InvocationHandler {
private final JdkDynamicProxy target;
public ProxyClass(JdkDynamicProxy target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("프록시 객체 실행");
Object result = method.invoke(target, args);
System.out.println("프록시 객체 종료");
return result;
}
}
실행
public static void main(String[] args) {
JdkDynamicProxy target = new TargetClass();
ProxyClass handler = new ProxyClass(target);
JdkDynamicProxy proxy = (JdkDynamicProxy) Proxy.newProxyInstance(JdkDynamicProxy.class.getClassLoader(),
new Class[]{JdkDynamicProxy.class},
handler
);
System.out.println(proxy.getClass());
proxy.execute();
}
-- 작업결과 --
class com.sun.proxy.$Proxy0
프록시 객체 실행
원본 객체 실행
프록시 객체 종료
CGLIB
는 Enhancer
의존성을 추가하여 사용할 수 있다.
- 코드 생성 라이브러리로 런타임에 동적으로 프록시를 생성해주는 기능을 제공.
- 리플렉션이 아닌 바이트코드를 조작한다. (리플렉션을 사용하는 JDK 동적 프록시보다 성능이 좋다)
- Default 생성자가 필요하다
- 클래스가 final일 경우 생성불가능.
- Target의 생성자가 두 번 호출된다.
의존성 추가
: spring aop 의존성을 받아서 처리..implementation 'org.springframework.boot:spring-boot-starter-aop'
TargetClass
: 원본 Target 객체public class TargetClass {
public void execute() {
System.out.println("원본 객체 실행");
}
}
CglibProxy
: 프록시 객체public class CglibProxy implements MethodInterceptor {
private final Object target;
public CglibProxy(Object target) {
this.target = target;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("프록시 객체 실행");
proxy.invoke(target, args);
System.out.println("프록시 객체 종료");
return null;
}
}
실행
public static void main(String[] args) {
TargetClass target = new TargetClass();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TargetClass.class);
enhancer.setCallback(new CglibProxy(target));
TargetClass proxy = (TargetClass)enhancer.create();
System.out.println(proxy.getClass());
proxy.execute();
}
--- 작업결과 ---
class com.example.demo.proxy.TargetClass$$EnhancerByCGLIB$$4b768b74
프록시 객체 실행
원본 객체 실행
프록시 객체 종료
- Dynamic Proxy를 생성한다.
- Interface 유무를 체크하여 JDK Dynamic Proxy / CGLIB 중에서 Proxy를 생성한다.
Interface
: JDK Dynamic Proxy 로 Proxy 생성Target target = new TargetImpl();
ProxyFactory proxyFactory = new ProxyFactory(target);
proxyFactory.addAdvice(new Proxy());
Target proxy = (Target) proxyFactory.getProxy();
proxy.execute();
Class
: CGLIB로 Proxy 생성Target target = new Target();
ProxyFactory proxyFactory = new ProxyFactory(target);
proxyFactory.addAdvice(new Proxy());
Target proxy = (Target) proxyFactory.getProxy();
proxyFactory.setProxyTargetClass(true);
: 인터페이스로 구현하더라도 이 설정이 되어있으면 CGLIB로 구현spring.aop.proxy-target-class=true
: Spring에서는 application.yml 파일과 같은 곳에 이 설정값으로 CGLIB로 구현할 지 여부를 정할 수 있다.AOP
를 구현하여 Proxy 객체를 이용할 수 있다.Pointcut
을 이용하여 어떤 Target
객체에 Proxy
를 적용할 지 간단하게 지정할 수 있다.이상.