Prxoy Patten
클라이언트가 RealSubject(실제 서비스에 필요한 코드)를 직접 호출하지 않고 Proxy(대리인)를 호출하여
Proxy(대리인)로 하여금 RealSubject를 대신 호출시키는 디자인 패턴이다.
간단한 예를 들어보자.
public class RealSubject {
public void hello() {
System.out.println("안녕하세요");
}
public void bye() {
System.out.println("다음에 만나요");
}
}
RealSubject 에는 hello() , bye()라는 중요한(?) 메서드가 있고 우리는 이 중요한 메서드를
서비스를 제공하기 전에 성능 측정을 해보고 싶다.
다음은 Proxy 패턴을 쓰지 않고 성능을 측정한 코드이다.
public class RealSubject {
public void hello() throws InterruptedException {
long l = System.currentTimeMillis();
Thread.sleep(1000);
// 난 이 로직만 필요한데??
System.out.println("안녕하세요");
System.out.println(System.currentTimeMillis()-l);
}
public void bye() throws InterruptedException {
long l = System.currentTimeMillis();
Thread.sleep(1000);
// 난 이 로직만 필요한데??
System.out.println("다음에 만나요");
System.out.println(System.currentTimeMillis()-l);
}
}
문제점 1)
나의 중요한 메서드에 실제 서비스에는 필요 없는 성능 측정하는 로직이 추가되었고 복잡해 보인다.
그리고 서비스를 제공할 때는 지워서 서버에 올려야 한다.
문제점 2)
또한 RealSubject의 메서드가 hello() , bye() 뿐만 아니라 100개가 더 추가된다면
아래와 같은 로직이 100개나 중복으로 생성된다.
public void moreMethod() throws InterruptedException {
long l = System.currentTimeMillis();
Thread.sleep(1000);
// ~~~ 구현된 로직들
System.out.println(System.currentTimeMillis()-l);
}
이제 프록시 패턴을 이용하여 문제점을 하나씩 고쳐 가보자
먼저 RealSubject 가 구현하고 있는 method 들을 다시 인터페이스로 정의를 한다.
public interface Subject {
void hello() throws InterruptedException;
void bye() throws InterruptedException;
}
그러면 RealSubject는 Subject라는 인터페이스를 구현하고 있다.
public class RealSubject implements Subject {
@Override
public void hello() {
System.out.println("안녕하세요");
}
@Override
public void bye() {
System.out.println("다음에 만나요");
}
}
이제 성능을 측정하는 Proxy Class를 생성하자
public class ProxySubject implements Subject {
private final Subject subject;
public ProxySubject(Subject subject) {
this.subject = subject;
}
@Override
public void hello() throws InterruptedException {
long l = System.currentTimeMillis();
Thread.sleep(1000);
//생성자로 주입받은 subject 한테 서비스를 위임하고 있음.
subject.hello();
System.out.println(System.currentTimeMillis()-l);
}
@Override
public void bye() throws InterruptedException {
long l = System.currentTimeMillis();
Thread.sleep(1000);
//생성자로 주입받은 subject 한테 서비스를 위임하고 있음.
subject.bye();
System.out.println(System.currentTimeMillis()-l);
}
}
proxySubject 클래스는 현재 성능을 측정하는 로직만을 구현하고 있고 실제 서비스는 subject 한테 위임하고 있다.
그럼 전체적인 구조는 다음과 같아진다.
다음과 같은 구조를 가짐으로써 RealSubject 클래스는 서비스에 필요한 로직만을 가지게 되었다.
main 메서드는 다음과 같습니다.
public static void main(String[] args) throws InterruptedException {
Subject subject = new ProxySubject(new RealSubject());
//성능 측정이 필요없을때는 RealSubject를 주입 시켜주면됨
//Subject subject = new RealSubject();
subject.hello();
subject.bye();
}
지금 구현한 Proxy patten으로는 문제점 1번을 해결할 수 있지만 문제점 2번을 해결 할 수 없다.
또한 ProxyClass(ProxySubject.class)를 직접 작성해야 하고 관리를 해야 한다.
그래서 자바에서 제공하는 Reflection을 이용하여 Dynamic Proxy를 만들어 볼 것이다.
Reflection 글도 나중에 작성하겠습니다.
Reflection 이란 간단히 컴파일 시점에서 객체를 생성하지 않고 런타임 시점에서 객체를 생성할 수 있는 기법이다.
Dynamic Proxy
Reflection 기법을 어느 정도 알고 있다고 가정하고 설명하겠습니다.
일단 사용법부터 알아보자.
우선 Proxy.newProxyInstance() 메서드부터 살펴보자
java.lang.reflect.Proxy class에서 newProxyInstance() 메서드는 파라미터 값으로
(ClassLoader , Class <?>[] interfaces , InvocationHandler) 를 받는다
ClassLoader : 프록시 클래스를 정의하는 클래스 로더 -> 저희는 Subject.class.getClassLoader()가 됩니다.
Class <?>[] interfaces : 프록시 클래스에서 구현할 인터페이스 목록들 -> new Class [] {Subject.class}
InvocationHandler : 메서드를 호출하고 컨트롤할 핸들러입니다.
여기서 InvocationHandler는 Functional Interface로 볼 수 있으며 자바 8 이상 버전부터 람다식으로 간단히 표현 가능합니다.
// 여기서 Object 는 RealSubject.class 가 됩니다.
private static InvocationHandler invocationHandler(Object o){
return (proxy, method, args) -> {
long l = System.currentTimeMillis();
Thread.sleep(1000);
Object invoke = method.invoke(o, args);
System.out.println(System.currentTimeMillis()-l);
return invoke;
};
}
람다식으로 InvocationHandler를 리턴하는 static 메서드 생성
여기서 invocationHandler를 통해 RealSubject 가 가지고 있는 method 앞뒤로 원하는 로직을 추가할 수가 있다.
또한 method.getName() 통한 method 별로 원하는 로직도 추가하는 것이 가능하다.
private static InvocationHandler invocationHandler(Object o){
return (proxy, method, args) -> {
if(method.getName().equals("hello")) {
long l = System.currentTimeMillis();
Thread.sleep(1000);
Object invoke = method.invoke(o, args);
System.out.println(System.currentTimeMillis() - l);
return invoke;
}
return method.invoke(o,args);
};
}
이 처럼 reflection을 이용하여 Subject를 구현하고 있는 ProxySubject를 일일이 구현을 하지 않아도 ProxyPatten을 구현할 수 있고 중복 코드도 생기지 않는다. 문제점 2번을 해결할 수 있다.
main 메서드는 다음과 같습니다.
public static void main(String[] args) throws InterruptedException {
Subject subject= (Subject) Proxy.newProxyInstance(
Subject.class.getClassLoader(),
new Class[]{Subject.class},
Solution.invocationHandler(new RealSubject()));
subject.hello();
subject.bye();
}
'Java' 카테고리의 다른 글
[Java]XOR 이용하기 (0) | 2020.11.29 |
---|---|
[Java]equals , hashCode 사용하기 (0) | 2020.11.25 |
[Java]reflection 을 이용한 간단한 DI 프레임워크 만들기 (1) | 2020.08.17 |
[Java]SingleTonClass 연습 (0) | 2020.05.27 |
[Java]비정방형 배열 (0) | 2020.05.12 |