[목차]
1. 프록시 패턴이란?
2. 프록시 패턴은 왜 사용하는 것일까?
3. 프록시 패턴의 종류
3-1. 가상 프록시
3-2. 원격 프록시
3-3. 보호 프록시
4. 구현을 보기 전에 알아두면 좋은 부분
5. 프록시 패턴의 구현 방법
5-1. 가상 프록시
5-2. 보호 프록시
6. 결론
| Proxy Pattern - 프록시 패턴이란?
- 프록시(Proxy)는 사전적 용어로써 '대리인'이라는 의미를 지니고 있습니다. 그러므로 프록시 패턴이라는 용어를 본래 자신이 해야할 역할을 다른 무엇인가가 대신 하는 것이라는 뜻으로 접근할 수 있습니다. 또한 이는 어떤 객체에 대한 접근을 제어하는 수단으로써 대리인에 해당하는 객체를 제공하는 패턴으로 의미를 확장할 수 있습니다.
- 프록시 패턴은 어떠한 객체에 대한 접근을 제어하는 용도로 객체의 대리인 역할을 하는 객체를 제공하는 패턴입니다.
- 스프링 AOP의 공통의 기능을 구현할 때 프록시 패턴을 이용하기도 합니다.
| Proxy Pattern은 왜 사용하는 것일까?
- 실제 객체를 수행하기 이전에 전처리를 하거나, 기존 객체를 캐싱할 수 있습니다. 기존 객체의 리소스가 무거울 경우, 이러한 캐싱 과정을 통해 부하를 줄일 수 있는 장점이 있습니다.
- 기존 객체를 수정하지 않고 일련의 로직을 프록시 패턴을 통해 추가할 수 있습니다.
- 프록시는 기존 객체와 클라이언트의 요청 사이에 위치해 있기 때문에 하나의 방패(보안) 역할을 하기도 합니다.
[Proxy Pattern의 단점]
- 프록시 내부에서 객체 생성을 위해 쓰레드가 생성되거나 혹은 동기화를 구현해야할 경우 성능이 저하될 수 있습니다.
- 프록시 패턴을 다수 이용할 경우, 다수의 객체가 추가적으로 생성되며 그 수만큼 한단계 더 거치게 되므로 성능이 저하 될 수 있습니다.
| Proxy Pattern의 종류
- 가상 프록시(Virtual Proxy)
- 생성하기 힘든, 비용이 많이 드는 객체를 대신하는 역할을 맡습니다.
- 실제 객체의 사용 시점을 제어할 수 있습니다. 클라이언트가 처음 요청 및 접근할 때만 실제 객체가 생성되며 이후는 프록시를 참조하여 실제 객체를 대신할 수 있습니다.
- 원격 프록시(Remote Proxy)
- 원격 프록시는 다른 JVM에 위치한 객체와 정보를 주고 받습니다.
- 원격 프록시의 메소드를 호출하게 되면 네트워크를 통해 전달되어 원격 객체의 메소드가 호출되며 이러한 결과는 다시 클라이언트에게 전달됩니다.
- 보호 프록시(Protection Proxy)
- 객체에 대해 액세스(접근) 할 수 있는 권한을 부여하는 것입니다.
- 민감한 객체에 대해 접근을 제어할 수 있습니다.
-
| 여기서 잠깐! 구현 방법을 보기 전에 이것만큼은 간략히 알고 가자!
싱글톤 패턴의 구현 방법을 설명하다보면, 메모리나 바인딩, 객체 지향 설계에 관한 여러 이야기를 함께 할 수 밖에 없습니다. 그러므로, 사전 지식으로 간략하게 아 이런거구나! 하는 짧은 정리를 보고 아래 글을 읽는 다면 보다 잘 이해가 될 것입니다.
1. 프록시 패턴 vs 데코레이터 패턴
- 프록시 패턴(Proxy Pattern) : 어떠한 클래스에 대한 접근을 제어 혹은 접근에 초점에 맞춘 용도로 쓰입니다.
- 데코레이터 패턴(Decorator Pattern) : 클래스에 새로운 행동 or 기능을 추가하기 위한 용도로 쓰입니다.
2. 프록시 패턴 vs 어댑터 패턴
- 프록시 패턴과 어댑터 패턴 모두 클라이언트와 객체 사이에 위치하여 클라이언트의 요청을 받아서 다른 객체에게 전달해주는 역할을 합니다.
- 하지만, 어댑터 패턴은 다른 객체의 인터페이스를 바꿔주지만, 프록시 패턴에서는 똑같은 인터페이스를 사용합니다.
| 프록시 패턴의 구현 방법
1. 가상 프록시
- 인터페이스를 통해 RealSubject와 Proxy객체가 사용할 메소드를 정의합니다. 이후, Proxy객체에서 제어 혹은 기능을 추가하여 구현할 수 있습니다.
public interface ProxyInterface {
void printStatement();
}
public class RealSubject implements ProxyInterface {
@Override
public void printStatement() {
System.out.println("Proxy Check");
}
}
public class Proxy implements ProxyInterface {
ProxyInterface proxyInterface;
@Override
public void printStatement() {
proxyInterface = new RealSubject();
System.out.println("Here is Proxy Class");
proxyInterface.printStatement();
}
}
public class Main {
public static void main(String[] args) {
ProxyInterface proxyInterface = new Proxy();
proxyInterface.printStatement();
}
}
2. 보호 프록시
- User와 NotUser가 있을 때 getter() 메소드와 setter() 메소드에 대한 권한을 구현한 예제입니다.
- 객체에는 name(이름) 변수가 들어가 있습니다.
- 권한과 관련해서는 User는 getter() 메소드와 setter() 메소드 모두 권한이 있지만 NotUser는 setter()메소드에 대한 권한이 없습니다.
- InvocationHandler 인터페이스의 invoke() 메소드를 이용하여서 구현할 수 있습니다.
- invoke() 메소드의 첫번째 인자 : 생성된 프록시 객체의 참조
- invoke() 메소드의 두번째 인자 : 프록시를 통해 호출된 메소드의 참조
- invoke() 메소드의 세번째 인자 : 해당 메소드의 인자값의 참조
public interface ProtectionInterface {
String getName();
void setName(String name);
}
public class ProtectionSubjectImpl implements ProtectionInterface {
String name;
public ProtectionSubjectImpl(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class UserHandler implements InvocationHandler {
ProtectionInterface protectionInterface;
public UserHandler(ProtectionInterface protectionInterface) {
this.protectionInterface = protectionInterface;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().startsWith("get")) {
return method.invoke(protectionInterface, args);
} else if (method.getName().startsWith("set")) {
return method.invoke(protectionInterface, args);
}
return null;
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class NotUserHandler implements InvocationHandler {
ProtectionInterface protectionInterface;
public NotUserHandler(ProtectionInterface protectionInterface) {
this.protectionInterface = protectionInterface;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().startsWith("get")) {
return method.invoke(protectionInterface, args);
} else if (method.getName().startsWith("set")) {
System.out.println("유저에게 접근권한이 없습니다.");
}
return null;
}
}
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
ProtectionInterface protectionInterface = new ProtectionSubjectImpl("이수형");
ProtectionInterface userProxy = getProxy(protectionInterface);
System.out.println("TEST");
System.out.println(userProxy.getName());
userProxy.setName("User");
System.out.println(userProxy.getName());
System.out.println();
System.out.println();
ProtectionInterface notUserProxy = getNotUserProxy(protectionInterface);
System.out.println("TEST");
System.out.println(notUserProxy.getName());
notUserProxy.setName("Not User");
System.out.println(notUserProxy.getName());
}
private static ProtectionInterface getProxy(ProtectionInterface protectionInterface) {
return (ProtectionInterface) Proxy.newProxyInstance(
protectionInterface.getClass().getClassLoader(),
protectionInterface.getClass().getInterfaces(),
new UserHandler(protectionInterface));
}
private static ProtectionInterface getNotUserProxy(ProtectionInterface protectionInterface) {
return (ProtectionInterface) Proxy.newProxyInstance(
protectionInterface.getClass().getClassLoader(),
protectionInterface.getClass().getInterfaces(),
new NotUserHandler(protectionInterface));
}
}
4. 그 외의 프록시 패턴 종류
- 앞서 언급한 3개의 프록시를 제외하고 동기화 프록시, 스마트 레퍼런스 프록시, 지연 복사 프록시 등 다양한 프록시가 존재합니다.
- 하지만, 프록시의 종류가 달라진다 하더라도 기본 개념은 변하지 않으며 응용된 형태입니다.
| Conclusion
Proxy Pattern이란?
- 객체를 직접적으로 참조하는 것이 아니라, 여러가지의 목적으로 구현된 대리역할의 객체를 통해 기능을 수행하는 것을 의미합니다.
왜 Proxy Pattern을 사용하는가?
- 메모리 낭비를 방지
- 데이터 공유를 해야하는 경우 효율성 고려
Proxy Pattern의 종류
- 가상 프록시
- 원격 프록시
- 보호 프록시
- 스마트 레퍼런스 프록시
- 등등
Proxy Pattern을 공부하며
- 프록시 패턴을 공부하며 다양한 종류에 한번 놀랐고, 각각이 지닌 역할이 다양하다는 것에 대해서 또 한번 놀랐습니다. 대표적인 프록시 패턴의 종류에 대해서만 짚었지만 나중에 그 외의 프록시 패턴에 대해서도 알아보고 구현하면 좋은 공부가 될 것이라고 생각합니다.
- 디자인 패턴 시리즈를 이어갈때마다 개념을 충분히 이해하고 그에 따른 구현 과정이 쉽지 않았습니다. 또한, 작성하려는 문장과 개념이 과연 옳은 것인가 싶어 확실하다 생각될 때까지 반복적으로 공부했는데 저 또한 많이 배울 수 있었던 좋은 시간이었습니다.
(참고)
'Develop > JAVA' 카테고리의 다른 글
Java 디자인 패턴 네번째 이야기 - 팩토리 메소드 패턴(Factory Method Pattern) (0) | 2021.08.16 |
---|---|
Java 디자인 패턴 세번째 이야기 - 빌더 패턴(Builder Pattern) (0) | 2021.08.13 |
Java 디자인 패턴 첫번째 이야기 - 싱글톤 패턴(Singleton Pattern) (0) | 2021.08.07 |
Object 클래스의대표적인 메소드(equals(),hashcode(),toString()) (0) | 2021.08.04 |
String과 StringBuffer 그리고 StringBuilder의 차이 (0) | 2021.08.03 |