CH04 다이나믹 프록시
프록시 패턴
이미 다른 포스팅에서도 여러번 다뤘다. 핵심은 프록시 패턴은 리얼 서브젝트의 SRP를 지켜줌과 동시에 부가적인 기능을 프록시에 추가할 수 있다는 것이다.

SRP 원칙에 따라서 리얼 서브젝트에는 뭔가를 추가하는 것을 피하고 싶지만 뭔가 부가적인 것이 필요할때 SRP를 준수하면서 기능도 추가하여 이를 해결할 수 있는 패턴이다.
간단한 예제 코드를 통해서 프록시 패턴을 구현해보자. Cafe 라는 서브젝트를 구현한 Starbucks라는 리얼 서브젝트를 만들고 Starbucks를 감싸는 StarbucksProxy를 구현해보는 실습이다.
public interface Cafe {
void makeCoffee();
}
public class Starbucks implements Cafe {
public Starbucks() {
}
@Override
public void makeCoffee() {
System.out.println("cafe latte :D");
}
}
public class StarbucksProxy implements Cafe {
Starbucks starbucks;
public StarbucksProxy(Starbucks starbucks) {
this.starbucks = starbucks;
}
@Override
public void makeCoffee() {
System.out.println("drive through in---");
starbucks.makeCoffee();
System.out.println("drive through out---");
}
}
public class App {
public static void main(String[] args) {
StarbucksProxy starbucksProxy = new StarbucksProxy(new Starbucks());
starbucksProxy.makeCoffee();
}
}
9:37:04 오전: Executing task 'App.main()'...
> Task :compileJava
> Task :processResources NO-SOURCE
> Task :classes
> Task :App.main()
drive through in---
cafe latte :D
drive through out---
기본적인 프록시 패턴의 형태인데, 리얼 서브젝트인 Starbucks를 수정하지 않고 StarbucksProxy를 이용해서 드라이브 스루를 구현했지만 기능이 더 추가가 되면 계속되는 위임의 형태로 중복 코드가 발생하기도 하는 등 불편함이 있다.
그래서 SRP를 준수하되 기능 추가가 필요한 경우 매번 클래스를 만들 것이 아니라 런타임시에 동적으로 생성해내는 방법인 다이나믹 프록시 를 사용한다.
A dynamic proxy class is a class that implements a list of interfaces specified at runtime such that a method invocation through one of the interfaces on an instance of the class will be encoded and dispatched to another object through a uniform interface.
프록시와 다이나믹 프록시 차이
위에서 구현한 일반적인 프록시의 경우 새로운 클래스 자체를 만든 경우이다. 다이나믹 프록시는 '런타임에, 인스턴스를' 프록시 형태로 만드는 것을 의미한다.
다이나믹 프록시 실습
바로 위에서 프록시 패턴을 실습한 것은 컴파일 타임에 이미 프록시 코드가 있었던 것이고, 이를 동적으로 런타임때 프록시 형태로 구현하도록 바꿔보자.
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class App {
public static void main(String[] args) {
// StarbucksProxy starbucksProxy = new StarbucksProxy(new Starbucks());
// starbucksProxy.makeCoffee();
Cafe cafe = (Cafe) Proxy.newProxyInstance(Cafe.class.getClassLoader(), new Class[]{Cafe.class}, new InvocationHandler() {
Starbucks starbucks = new Starbucks();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("makeCoffee")) {
System.out.println("drive through in---");
Object invoke = method.invoke(starbucks, args);
System.out.println("drive through out---");
return invoke;
}
return method.invoke(starbucks, args);
}
});
cafe.makeCoffee();
}
}
위와 같이 동적으로 만든 프록시 객체를 두고 다이나믹 프록시라고 한다. 정확히는 이것 역시 그냥 프록시이지만 다이나믹하게 생성된 프록시라고 표현하는게 맞겠다. 아무튼 핵심은 미리 코드를 통해서 클래스 파일을 만들어 둔 것이 아니라 '동적으로, 런타임에, 인스턴스를' 생성하는 것이다.
위의 방식은 인터페이스의 프록시 밖에 만들지 못한다. 클래스의 다이나믹 프록시를 구현하기 위해서는 아래의 방식을 사용해야 한다.
이 방식을 이용하면 인터페이스 뿐만이 아니라 클래스도 다이나믹 프록시로 감싸서 원하는 기능을 구현할 수 있다. cglib이라는 라이브러리를 사용하는 방법인데 스프링, 하이버네이트가 사용하는 라이브러리이다.
implementation group: 'cglib', name: 'cglib', version: '3.3.0'
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class App {
public static void main(String[] args) {
MethodInterceptor methodInterceptor = new MethodInterceptor() {
Starbucks starbucks = new Starbucks();
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
if (method.getName().equals("makeCoffee")) {
System.out.println("drive through in---");
Object invoke = method.invoke(starbucks, args);
System.out.println("drive through out---");
return invoke;
}
return method.invoke(starbucks, args);
}
};
Cafe cafe = (Cafe) Enhancer.create(Cafe.class, methodInterceptor);
cafe.makeCoffee();
}
}
다이나믹 프록시 사용처
스프링 데이터 JPA
스프링 AOP
Mockito
하이버네이트 lazy initialzation
etc
Last updated