프록시 패턴?
프록시는 대리인이라는 뜻으로, 누군가를 대신해서 그 역할을 수행하는 존재이다.
프로그래밍에서는 어떤 객체를 사용할 때, 객체를 직접적으로 참조하는 것이 아닌 해당 객체를 대항하는 객체를 통해 객체에 접근하는 방식을 사용하면 해당 객체가 메모리에 존재하지 않아도 기본적인 정보를 참조하거나 설정할 수 있고, 실제 객체의 기능이 필요한 시점까지 객체의 생성을 미룰 수도 있다.
프록시 코드 예제
public interface Subject {
void request();
}
public class RealSubject implements Subject{
@Override
public void request() {
System.out.println("request start");
}
}
public class ProxySubject implements Subject{
private Subject subject;
@Override
public void request() {
if(this.subject == null)subject = new RealSubject(); // 실제로 실행될 때까지 객체의 생성을 늦춤
System.out.println("before request"); // 메인 로직 실행 전 로그
subject.request(); // 메인 로직은 real 객체에 위임
System.out.println("after request"); // 메인 로직 실행 후 로그
}
public ProxySubject(String request) {
valid(request); // 예외 처리
}
private void valid(String request){
if( request.equals("Wrong Request"))throw new RuntimeException();
}
}
public class Main {
public static void main(String[] args) {
Subject subject = new ProxySubject("request");
subject.request();
}
}
RealSubject를 이용하고 싶은 사용자가 RealSubject에 접근하는게 아닌 ProxySubject로 접근해서 사용한다.
프록시 패턴의 특징
장점
OCP를 지킬 수 있다.
기존 코드를 변경하지 않고 새로운 기능을 추가해 변경에 닫혀있도록 한다.
SRP를 지킬 수 있다.
기존 코드가 해야하는 로직만 하도록 유지하고, 로그, 예외 처리같은 로직 외의 것들을 처리할 수 있다.
단점
객체를 생성할 때 한단계를 더 거치게 되므로 , 빈번한 객체 생성시 성능이 저하될 수 있다.
프록시 내부에서 객체 생성을 위해 스레드가 생성, 동기화가 구현되어야하는 경우 성능이 저하될수 있다.
가독성을 떨어뜨릴 수 있다.
다이나믹 프록시
런타임에 특정 인터페이스를 구현하는 클래스 또는 인터페이스를 만드는 기술
매번 프록시 클래스를 만드는게 아니라 로딩이 끝나면 힙에 올라오는 클래스 인스턴스를 통해 구현할 수 있다.
작성 포스팅 리플렉션 API
public interface Subject {
void request();
}
public class RealSubject implements Subject{
@Override
public void request() {
System.out.println("request start");
}
}
인터페이스와 실제 구현체까지는 똑같다.
public class Main {
Subject subject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class[]{Subject.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Subject subject = new RealSubject();
System.out.println("before request");
Object invoke = method.invoke(subject, args);
System.out.println("after request");
return invoke;
}
});
@Test
public void test(){
subject.request();
}
}
필요한 곳에서 Proxy.newProxyInstance 메소드를 사용해 프록시 클래스를 사용하지 않고 프록시 패턴을 적용할 수 있다.
하지만 자바는 인터페이스만 프록시를 지원하기 때문에 클래스에 프록시를 적용시켜주고싶다면 스프링, 하이버네이트에서 사용하는 CGLIB이나 ByteBuddy 라이브러리를 이용해야 한다.
CGLIB 이용한 클래스 프록시 코드 예제
public class RealSubject {
public void request() {
System.out.println("request start");
}
}
public class Main {
@Test
public void test(){
MethodInterceptor handler = new MethodInterceptor() {
RealSubject subject = new RealSubject();
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("before request");
Object invoke = method.invoke(subject, args);
System.out.println("after request");
return invoke;
}
};
RealSubject subject = (RealSubject) Enhancer.create(RealSubject.class, handler);
subject.request();
}
}
다이나믹 프록시 사용시 알아야 할 것
자바 다이나믹 프록시
인터페이스만 사용 가능하고, 인터페이스 구현체가 존재해야한다.
CGLIB, ByteBuddy 라이브러리를 사용한 프록시
클래스에도 사용 가능하지만 상속을 이용하므로 final 클래스나 private 생성자를 가진 클래스엔 적용할 수 없다.
참고
https://www.inflearn.com/course/the-java-code-manipulation/dashboard
https://ko.wikipedia.org/wiki/%ED%94%84%EB%A1%9D%EC%8B%9C_%ED%8C%A8%ED%84%B4