no_mkd
一。프록시 패턴이란 무엇인가?
이름 그대로, 프록시는 제 3 자입니다. 예를 들어 연예인의 매니저처럼, 연예인의 일은 모두 매니저가 처리하고, 연예인은 매니저에게 무엇을 할지 알려주기만 하면, 매니저가 자연스럽게 방법을 찾아 수행하고, 완료 후 결과를 연예인에게 보고하면 됩니다
원래는 호출자와 피호출자 간의 직접적인 상호작용이었지만, 이제는 호출자와 피호출자를 분리하고 프록시가 정보를 전달하여 호출을 완료합니다
二。프록시 패턴의 용도는 무엇인가?
프록시 패턴은 큰 패턴이므로 응용 범위가 넓습니다. 프록시의 종류에서 알 수 있습니다:
원격 프록시: 가장 고전적인 프록시 패턴 중 하나로, 원격 프록시는 원격 JVM 과 통신하여 로컬 호출자와 원격 피호출자 간의 정상적인 상호작용을 실현합니다
가상 프록시: 거대한 객체를 대신하여 필요한 경우에만 생성되도록 보장합니다
보호 프록시: 피호출자에게 액세스 제어를 제공하고 호출자의 권한을 확인합니다
이 외에도 방화벽 프록시, 스마트 참조 프록시, 캐시 프록시, 동기화 프록시, 복잡 은닉 프록시, 복사 시 쓰기 프록시 등이 있으며, 각각 특수한 용도가 있습니다
P.S.원격 프록시는 가장 기본적인 프록시 패턴이므로, 따로拿出来 설명할 필요가 있어, 따라서 본고에서 자세히 해석하고, 나머지 프록시는보충 블로그 글에서 상세히 설명합니다
三。원격 프록시
有些事情不用代理也能轻松解决,但有些事情必须得依靠代理来完成,比如要调用另一台机器上的一个方法,我们可能就不得不用代理
원격 프록시의 내부 메커니즘은 ���음과 같습니다:

설명하면, Stub 은 "스텁"으로, Server 객체를 나타냅니다
Skeleton 은 "스켈레톤"(왜 "스텁"과 "스켈레톤"이라고 불리는지 모르겠지만, 물론, 알 필요도 없습니다) 으로, Client 를 나타냅니다
Stub 은 분명히 클라이언트 쪽에 있는데, 왜 클라이언트의 프록시가 아니라 서비스의 프록시인가? 클라이언트는 서버와 상호작용해야 하고, 현재 서비스는 원격 JVM 에 있어 상호작용할 수 없으므로, Stub 을 사용하여 Server 를 나타내고, Stub 을 호출하는 것은 Server 를 호출하는 것과 동일합니다 (내부 통신 메커니즘은 Client 에게 투명하며, Client 에게 Stub 을 호출하는 것과 직접 Server 를 호출하는 것은 차이가 없습니다. 이것이 바로 프록시 패턴의 장점 중 하나입니다)
구체적인 흐름은 다음과 같습니다:
- Client 가 Stub 에 메서드 호출 요청을 전송 (Client 는 Stub 이 Server 라고 생각함)
- Stub 이 요청을 받아, Socket 을 통해 서비스 단의 Skeleton 과 통신하고, 호출 요청을 Skeleton 에 전달
- Skeleton 이 요청을 받아, 로컬 Server 를 호출 (조금 이상하게 들리지만, 여기서 Server 는 Service 에 해당함)
- Server 가 해당 동작을 수행하고, 결과를 호출자 Skeleton 에 반환
- Skeleton 이 결과를 받은 후, Socket 을 통해 Stub 에 전송
- Stub 이 결과를 Client 에 전달
P.S.2 단계와 5 단계는 모두 Socket 통신을 통해 수행해야 하며, 상호 전달하는 것은 모두 전송 전 직렬화하고, 수신 후 역직렬화해야 합니다.这也解释了为什么 Server 中的 public 方法返回值都必须是可序列化的
四。원격 프록시 구현
원격 프록시를 구현하는 두 가지 방법이 있습니다:
- Stub 과 Skeleton 을 커스텀 (내부 통신 구현)
- Java 가 지원하는 RMI(Remote Method Invocation) 를 이용하여 구현. 많은 수고를 덜 수 있지만, 내부 원리를 이해하기 어렵습니다
먼저 커스텀 방식의 예를 제시합니다. 이에 관한 좋은 블로그 글이 있어,勝手に 링크를 기록했습니다.여기를 클릭하여 이동>>
원문은 완전한 예를 제시했으므로, 여기서는 반복하지 않고, 원문에 의사 클래스 그림을 추가하여 이해를 돕습니다:

(왜 클래스 그림이 이렇게 그려졌는지 묻지 마십시오. "의사" 클래스 그림이라고 했잖아요..)
원문을 자세히 보면 원격 프록시를 이해하기 어렵지 않습니다. Stub 과 Skeleton 은 통신을 담당하며, Socket 으로 작성한 채팅 프로그램과 유사하고, 그 외에는 특별한 것이 없습니다
다음으로 Java 가 지원하는 RMI 를 이용하여 프록시 패턴을 구현하는 예를 제시합니다. 많은 세부 사항이 숨겨져 있음을 분명히 느낄 수 있습니다
먼저 원격 인터페이스를 정의해야 합니다:
package ProxyPattern;import java.rmi.RemoteException;
/**
-
서비스 인터페이스 정의 (java.rmi.Remote 인터페이스 확장)
-
@author ayqy / public interface Service extends java.rmi.Remote{ / 1.메서드 반환 타입은 직렬화 가능한 Serializable 이어야 함
- 2.모든 메서드는 예외 throws RemoteException 을 선언해야 함 (RMI 방식이므로)
- */
/**
- @return 완전한 인사 문장
- @throws RemoteException */ public String greet(String name) throws RemoteException; }
주의: 서비스 인터페이스 내의 public 메서드의 반환 타입은 직렬화 가능해야 합니다 (다시 말해, 커스텀된 반환 타입은 Serializable 인터페이스를 구현해야 함). String 타입은 이미 Serializable 인터페이스를 구현했습니다
왜 이렇게 java.rmi.Remote 를 확장하는 인터페이스를 정의해야 하는가? API 문서에 명확한 설명이 있습니다:
The Remote interface serves to identify interfaces whose methods may be invoked from a non-local virtual machine. Any object that is a remote object must directly or indirectly implement this interface. Only those methods specified in a "remote interface", an interface that extends java.rmi.Remote are available remotely.
간단히 말해, 컴파일러에 알리기 위한 것입니다: 우리의 Service 객체는 원격 호출 가능합니다. 그것뿐입니다
원격 인터페이스를 정의했으니, 물론 구체적인 구현도 필요합니다:
package ProxyPattern;import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject;
/**
-
원격 서비스 구현 (UnicastRemoteObject 확장 및 커스텀 원격 인터페이스 구현)
-
@author ayqy */ public class MyService extends UnicastRemoteObject implements Service{
/**
- 프로그램 버전 검증에 사용 (수신 측은 역직렬화 시 UID 를 검증하며, 일치하지 않으면 예외 발생) */ private static final long serialVersionUID = 1L;
/**
- 빈 생성자, 예외 선언만 위해 (기본 생성자는 예외를 선언하지 않음)
- @throws RemoteException */ protected MyService() throws RemoteException { }
@Override public String greet(String name) throws RemoteException { return "Hey, " + name; } }
P.S.서비스가 UnicastRemoteObject 클래스를 상속하는 것은 Stub 클래스를 자동 생성하기 위함입니다 (UnicastRemoteObject 는 구체적인 생성 세부 사항을 캡슐화하여, 우리는 한 클래스의 작업량을 덜었습니다)
서비스 단에 서비스만으로는 부족하며, RMI 등록 서비스를 시작하고 원격 객체를 등록하여 클라이언트에서 호출할 수 있도록 하는 Server 가 필요합니다:
package ProxyPattern;import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry;
/**
- 서버 클래스 구현, 서비스 시작 및 서비스 객체 등록 담당
- @author ayqy */ public class Server { public static void main(String[] args){ try { //RMI 등록 서비스 시작, 포트를 1099 로 지정 (1099 는 기본 포트) LocateRegistry.createRegistry(1099); //서비스 객체 생성 MyService service = new MyService(); //service 를 RMI 등록 서버에 등록, 이름을 MyService 로 지정 Naming.rebind("MyService", service); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
LocateRegistry.createRegistry() 방식으로 서비스를 시작하는 것 외에도, 명령줄 방식으로 시작할 수 있습니다 (rmiregistry 명령). 효과는 같습니다
서비스 단 코드는 여기까지로 완료되었습니다. Socket 채팅 프로그램과 마찬가지로, 두 부분의 코드를 작성해야 합니다. Server 와 Client 입니다. 이제 클라이언트 구현을 시작합니다. 매우 간단하며, 테스트 클래스와 같습니다:
package ProxyPattern;/* 참고 자료:
- 1.JAVA RMI 사용법
- http://blog.csdn.net/afterrain/article/details/1819659
- 2.RMI 내부 원리
- http://www.cnblogs.com/yin-jingyu/archive/2012/06/14/2549361.html
- */ import java.rmi.Naming;
/**
- 클라이언트 클래스 구현
- @author ayqy
/
public class Client {
/*
-
원격 객체 검색 및 원격 메서드 호출 */ public static void main(String[] argv) { try { //다른 RMI 등록 서비스를 실행 중인 머신에서 MyService 객체를 검색하려면, IP 주소만 수정하면 됩니다 Service service = (Service) Naming.lookup("//127.0.0.1:1099/MyService");
//원격 메서드 호출 System.out.println(service.greet("SmileStone"));} catch (Exception e) { System.out.println("Client exception: " + e); } } }
-
P.S.우리는 직접 Naming.lookup() 을 사용하여 Stub 객체를 가져옵니다 (맞습니다, Stub 입니다. 실제 객체는 아직 다른 머신에 있으므로, 물론 가져올 수 없습니다. 여기서 가져온 것은 Service 의 Stub 프록시일 뿐입니다). 그런 다음 프록시의 메서드를 호출하여 결과를 가져옵니다
이세부 사항에 주의하십시오:
//다른 RMI 등록 서비스를 실행 중인 머신에서 MyService 객체를 검색하려면, IP 주소만 수정하면 됩니다
Service service = (Service) Naming.lookup("//127.0.0.1:1099/MyService");
비록 한 줄이지만, 두 가지 세부 사항이 숨겨져 있습니다:
- 클라이언트는 서비스 인터페이스 Service 를 알아야 합니다. 여기서는 로컬에서 같은 package下に 있으므로 신경 쓸 필요 없지만, 실제 응용에서는 Client 와 Server 가 분리되어 있으므로, Client 는 Service 인터페이스의 사본을入手해야 합니다.否则无法调用
- 클라이언트는 서버의 IP 와 포트 번호를 알아야 합니다 (통신이니까, 이것이 없으면 안 됩니다)
半天忙しくしましたが, 실행 결과를 봅시다 (먼저 Server 를 실행하고, 다음으로 Client 를 실행):

P.S.Java 가 지원하는 RMI 를 이용하여 원격 프록시를 구현하는 부분은,다른 사람의 블로그 글을 참고했습니다.里面解释的更详细一些
P.S.명령줄 방식으로 RMI 등록 서비스를 시작하는 것은, 너무 번거롭고, 게다가 먼저 Stub 클래스를 생성해야 하므로, 이 방식은 권장하지 않습니다. 구체적인 조작 세부 사항은, 위의 링크 블로그 글에도 자세히 소개되어 있으므로, 여기서는 반복하지 않습니다
주의: 테스트 결과를 눈으로 볼 때까지,我们始终没有自定义 Stub 与 Skeleton 类,不是吗?对,没错,它们确实存在,只是被 RMI 隐藏起来了(据说 Java1.5 之后 RMI 中没有了 Skeleton,甚至更高的版本中连 Stub 都没有了。。不过这都没关系,我们已经清楚了最原始的远程代理)
五。요약
돌아가서 원격 프록시가 무엇을 했는지 생각해 봅시다:
- 메서드 호출을 인터셉트하고 제어합니다 (이것도 프록시 패턴의 가장 큰 특징입니다. 가장 전형적인 것은, 방화벽 프록시..)
- 원격 객체의 존재는 클라이언트에게 투명합니다 (클라이언트는 Stub 프록시 객체를 완전히 원격 객체로 생각합니다.虽然客户有点好奇为什么可能会出现异常..)
- 원격 프록시는 통신 세부 사항을 숨깁니다
다른 머신 (JVM) 위의 지정된 객체의 메서드를 호출해야 할 때, 원격 프록시를 사용하는 것은 좋은 선택입니다..
아직 댓글이 없습니다