이미 다른 포스팅에서도 여러번 다뤘다. 핵심은 프록시 패턴은 리얼 서브젝트의 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();
}
}
기본적인 프록시 패턴의 형태인데, 리얼 서브젝트인 Starbucks를 수정하지 않고 StarbucksProxy를 이용해서 드라이브 스루를 구현했지만 기능이 더 추가가 되면 계속되는 위임의 형태로 중복 코드가 발생하기도 하는 등 불편함이 있다.
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이라는 라이브러리를 사용하는 방법인데 스프링, 하이버네이트가 사용하는 라이브러리이다.