no_mkd
시작하며:
프록시 패턴의 내부 원리, 역할 및 원격 프록시의 구현은 이전 포스팅에서 상세히 설명했습니다. 본문은 그 내용에 대한 보충으로, 다른 종류의 프록시들을 소개합니다.
1. 가상 프록시(Virtual Proxy)
먼저 가상 프록시의 역할을 명확히 합시다: 거대한 객체가 실제로 생성되기 전까지 가상 프록시가 그 자리를 대신합니다 (객체가 실제로 필요할 때만 생성되도록 보장하여 리소스 낭비를 방지합니다). 우리는 이미 가상 프록시를 자주 사용하고 있을지도 모릅니다. 예를 들어:
프로그램 내에 집중적인 계산이 필요하거나 서버로부터 대량의 데이터를 가져오는 것과 같이 시간과 리소스가 많이 드는 작업이 있을 수 있습니다. 보통은 먼저 기본 정보를 표시하거나 애니메이션을 보여주며 프로그램이 작업 중임을 알리고, 작업이 완료된 후 획득한 정보를 표시합니다. 여기에 가상 프록시의 아이디어가 담겨 있습니다. 가상 프록시는 작업 완료를 기다리는 동안의 업무를 별도의 가상 프록시 객체로 분리하여 캡슐화합니다. 클라이언트와 직접 상호작용하는 것은 실제 작업을 수행하는 객체가 아니라 가상 프록시입니다.
가상 프록시는 클라이언트의 요청을 받으면 다음과 같은 일을 수행합니다:
- 실제 객체가 존재하지 않는 경우(아직 생성되지 않았거나 생성 중인 경우) 클라이언트의 요청을 기록하고 기본 정보를 표시합니다. 실제 객체 생성이 완료될 때까지 기다린 후 요청을 실제 객체에 전달합니다.
- 실제 객체가 이미 존재하는 경우, 클라이언트의 요청을 즉시 실제 객체에 위임합니다.
- 실제 객체가 작업 결과를 반환하기 전까지 프록시 객체는 기본 정보 표시(또는 기본 처리)를 담당합니다.
- 작업 결과를 얻으면 클라이언트에게 직접 전달합니다. (맞습니다, 프록시의 임무는 '전달'과 '수신'입니다.)
프록시이므로 실제 객체로 위장해야 하며 클라이언트가 프록시의 존재를 눈치채지 못하게 해야 합니다. 따라서 가상 프록시의 클래스 다이어그램은 다음과 같습니다:

Client는 가상 프록시 객체 Proxy를 보유하고, Proxy는 구체적인 서비스 객체 MyService를 보유합니다.
중요한 점은 가상 프록시 Proxy와 구체적인 서비스 MyService 모두 Service 인터페이스를 확장(상속)한다는 것입니다. 따라서 프록시와 실제 객체는 동일한 행위를 가지며 클라이언트는 Proxy의 존재를 알아차리지 못합니다.
2. 보호 프록시(Protection Proxy)
보호 프록시는 매니저와 같은 역할을 합니다. (스타에게 직접 연락할 수는 없습니다. 저를 거쳐야 하며, 당신이 나쁜 사람이 아니라는 것을 확인한 후에야 연결해 드립니다.)
다르게 표현하자면, 보호 프록시는 실제 객체에 대한 접근 제어를 제공하여 정당한 호출자만이 서비스를 받을 수 있도록 보장합니다.
구현 측면에서 클래스 다이어그램은 위의 가상 프록시와 완전히 동일하지만, Proxy에 호출자의 권한을 판단하는 책임이 추가됩니다. (눈치채셨나요? 여러 프록시를 결합하여 사용할 수도 있습니다. 물론 이런 프록시는 단일 책임 원칙을 완벽히 만족하지는 않지만, 프록시들을 관리하는 또 다른 프록시를 만들어 해결할 수 있습니다.)
사실 Java API는 프록시 패턴에 대한 지원(동적 프록시)을 제공합니다. Java가 제공하는 동적 프록시는 Proxy 프록시 클래스(객체가 아닌 클래스입니다)가 런타임에 생성된다는 점에서 동적입니다. 우리는 Proxy의 내부 구현을 직접 수정할 수 없지만, InvocationHandler를 통해 Proxy가 어떻게 행동해야 할지 알려줄 수 있습니다.
동적 프록시를 사용하여 보호 프록시를 구현하는 기본 아이디어는 여러 프록시를 생성하고, 각 프록시가 서로 다른 권한을 가진 호출자에 대응하는 서비스를 나타내게 하는 것입니다.
보호 프록시 구현을 위한 상황을 설정해 봅시다: 게시글 추천(좋아요) 시스템입니다. 다른 사람의 글에는 추천할 수 있지만 본인의 글에는 추천할 수 없으며, 작성자와 방문자 모두 추천 수를 볼 수 있습니다.
상황은 단순하지만 문제를 설명하기에는 충분합니다. 시뮬레이션 구현을 시작해 보겠습니다. 먼저 BlogUser 인터페이스가 필요합니다:
package ProxyPattern.ProtectionProxy;/**
- 블로그 사용자 인터페이스 정의
- @author ayqy */ public interface BlogUser { public void support(); public int getSupportCount(); }
ConcreteUser 또한 필요하지만 구현이 간단하므로 생략합니다. 이제 동적 프록시를 생성합니다. 여기에는 작성자와 방문자라는 두 종류의 권한이 있으므로 최소한 두 개의 서로 다른 동적 프록시가 필요합니다.
AuthorHandler:
package ProxyPattern.ProtectionProxy;import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;
/**
-
작성자를 위한 InvocationHandler 구현
-
@author ayqy */ public class AuthorHandler implements InvocationHandler{ BlogUser user;
public AuthorHandler(BlogUser user){ this.user = user; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException {// 잘못된 접근 예외 선언 try { /* 작성자가 추천 수를 확인하려 하면 실제 객체 user에게 위임하여 처리 / if(method.getName().equals("getSupportCount")){ return method.invoke(user, args); } / 작성자가 본인 글에 추천하려 하면 예외를 던져 거부 */ if(method.getName().equals("support")){ throw new IllegalAccessException(); } } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); }
return null;// 다른 메서드 호출은 무시}
}
유사하게 VisitorHandler도 구현해야 합니다. 여러 프록시가 준비되었으니 이들을 관리할 '프록시의 프록시', 즉 보호 프록시를 구현합니다 (호출자 권한에 따라 대응하는 동적 프록시를 반환합니다).
ProtectionProxy:
package ProxyPattern.ProtectionProxy;import java.lang.reflect.Proxy;
/**
-
보호 프록시 구현
-
@author ayqy / public class ProtectionProxy { /*
- @param user 호출자
- @return 작성자에 대응하는 프록시 */ public BlogUser getAuthorProxy(BlogUser user){ return (BlogUser)Proxy.newProxyInstance(user.getClass().getClassLoader() , user.getClass().getInterfaces() , new AuthorHandler(user)); }
/**
- @param user 호출자
- @return 방문자에 대응하는 프록시 */ public BlogUser getVisitorProxy(BlogUser user){ return (BlogUser)Proxy.newProxyInstance(user.getClass().getClassLoader() , user.getClass().getInterfaces() , new VisitorHandler(user)); }
/* 여기에 다른 프록시 생성 메서드 추가 가능 */ }
이제 준비가 끝났습니다. 간단한 테스트를 진행해 봅니다:
package ProxyPattern.ProtectionProxy;/**
-
테스트 클래스 구현
-
@author ayqy */ public class Test { public static void main(String[] args){ // 보호 프록시 생성 ProtectionProxy proxy = new ProtectionProxy();
System.out.println("작성자 파트:"); // 작성자 대응 프록시 생성 BlogUser authorProxy = proxy.getAuthorProxy(new ConcreteUser()); System.out.println("작성자가 추천 총수 확인 시도"); System.out.println("추천 수: " + authorProxy.getSupportCount() + " 회"); // 작성자가 본인 글 추천 시도 System.out.println("작성자가 본인 글 추천 시도"); try{ authorProxy.support(); }catch(Exception e){ System.out.println("추천 실패: 본인 글에는 추천할 수 없습니다."); } System.out.println("시도 후 추천 수: " + authorProxy.getSupportCount() + " 회"); System.out.println("\n방문자 파트:"); // 방문자 대응 프록시 생성 BlogUser vistorProxy = proxy.getVisitorProxy(new ConcreteUser()); System.out.println("방문자가 추천 총수 확인 시도"); System.out.println("추천 수: " + vistorProxy.getSupportCount() + " 회"); // 방문자가 작성자 글 추천 시도 System.out.println("방문자가 작성자 글 추천 시도"); try{ vistorProxy.support(); }catch(Exception e){ System.out.println("추천 실패: 원인 불명"); } System.out.println("추천 후 추천 수: " + vistorProxy.getSupportCount() + " 회");} }
테스트 결과:

3. 기타 프록시
프록시 패턴은 매우 광범위하게 사용되어 일일이 열거하기 어렵습니다. 자주 쓰이는 몇 가지만 간단히 소개합니다:
| 프록시 명칭 | 역할 |
| 방화벽 프록시 | 네트워크 리소스 접근을 제어하여 '악성 클라이언트'로부터 주제를 보호합니다. |
| 스마트 참조 프록시 | 주제가 참조될 때 객체 참조 횟수 계산 등 추가적인 동작을 수행합니다. |
| 캐싱 프록시 | 비용이 큰 연산 결과를 임시 저장하고 여러 클라이언트가 결과를 공유하게 하여 연산이나 네트워크 지연을 줄입니다. |
| 동기화 프록시 | 멀티스레드 환경에서 주제에 대한 안전한 접근을 제공합니다. |
| 복잡성 은닉 프록시 (퍼사드 프록시) | 클래스 집합의 복잡성을 숨기고 접근을 제어합니다. (퍼사드 패턴보다 강력한 제어 제공) |
| Copy-on-Write 프록시 | 실제로 복제가 필요할 때까지 복제 작업을 지연시켜 객체 복제를 제어합니다. (가상 프록시와 유사) |
가장 기본적인 원격 프록시만 제대로 이해한다면, 이러한 변형 프록시들도 쉽게 이해할 수 있습니다.
아직 댓글이 없습니다