본문으로 건너뛰기

디자인 패턴 중 프록시 패턴 (Proxy Pattern)_원격 프록시 해석

무료2015-03-07#Design_Pattern#代理模式#Proxy Pattern

프록시 패턴은 제 3 자 (프록시 객체) 를 삽입하여 호출자와 피호출자 (실행자와는 다름) 를 분리합니다. 원격 프록시는 가장 고전적인 프록시 중 하나로, 피호출자가 로컬에 있지 않고 (다른 JVM 에 있음) 직접 호출할 수 없을 때 원격 프록시가 필요합니다. 호출자는 호출 요청을 원격 프록시에 전송하고, 프록시 객체가 피호출자와 통신한 후 호출 결과를 호출자에게 전달합니다

no_mkd

一。프록시 패턴이란 무엇인가?

이름 그대로, 프록시는 제 3 자입니다. 예를 들어 연예인의 매니저처럼, 연예인의 일은 모두 매니저가 처리하고, 연예인은 매니저에게 무엇을 할지 알려주기만 하면, 매니저가 자연스럽게 방법을 찾아 수행하고, 완료 후 결과를 연예인에게 보고하면 됩니다

원래는 호출자와 피호출자 간의 직접적인 상호작용이었지만, 이제는 호출자와 피호출자를 분리하고 프록시가 정보를 전달하여 호출을 완료합니다

二。프록시 패턴의 용도는 무엇인가?

프록시 패턴은 큰 패턴이므로 응용 범위가 넓습니다. 프록시의 종류에서 알 수 있습니다:

원격 프록시: 가장 고전적인 프록시 패턴 중 하나로, 원격 프록시는 원격 JVM 과 통신하여 로컬 호출자와 원격 피호출자 간의 정상적인 상호작용을 실현합니다

가상 프록시: 거대한 객체를 대신하여 필요한 경우에만 생성되도록 보장합니다

보호 프록시: 피호출자에게 액세스 제어를 제공하고 호출자의 권한을 확인합니다

이 외에도 방화벽 프록시, 스마트 참조 프록시, 캐시 프록시, 동기화 프록시, 복잡 은닉 프록시, 복사 시 쓰기 프록시 등이 있으며, 각각 특수한 용도가 있습니다

P.S.원격 프록시는 가장 기본적인 프록시 패턴이므로, 따로拿出来 설명할 필요가 있어, 따라서 본고에서 자세히 해석하고, 나머지 프록시는보충 블로그 글에서 상세히 설명합니다

三。원격 프록시

有些事情不用代理也能轻松解决,但有些事情必须得依靠代理来完成,比如要调用另一台机器上的一个方法,我们可能就不得不用代理

원격 프록시의 내부 메커니즘은 ���음과 같습니다:

설명하면, Stub 은 "스텁"으로, Server 객체를 나타냅니다

Skeleton 은 "스켈레톤"(왜 "스텁"과 "스켈레톤"이라고 불리는지 모르겠지만, 물론, 알 필요도 없습니다) 으로, Client 를 나타냅니다

Stub 은 분명히 클라이언트 쪽에 있는데, 왜 클라이언트의 프록시가 아니라 서비스의 프록시인가? 클라이언트는 서버와 상호작용해야 하고, 현재 서비스는 원격 JVM 에 있어 상호작용할 수 없으므로, Stub 을 사용하여 Server 를 나타내고, Stub 을 호출하는 것은 Server 를 호출하는 것과 동일합니다 (내부 통신 메커니즘은 Client 에게 투명하며, Client 에게 Stub 을 호출하는 것과 직접 Server 를 호출하는 것은 차이가 없습니다. 이것이 바로 프록시 패턴의 장점 중 하나입니다)

구체적인 흐름은 다음과 같습니다:

  1. Client 가 Stub 에 메서드 호출 요청을 전송 (Client 는 Stub 이 Server 라고 생각함)
  2. Stub 이 요청을 받아, Socket 을 통해 서비스 단의 Skeleton 과 통신하고, 호출 요청을 Skeleton 에 전달
  3. Skeleton 이 요청을 받아, 로컬 Server 를 호출 (조금 이상하게 들리지만, 여기서 Server 는 Service 에 해당함)
  4. Server 가 해당 동작을 수행하고, 결과를 호출자 Skeleton 에 반환
  5. Skeleton 이 결과를 받은 후, Socket 을 통해 Stub 에 전송
  6. 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;

/* 참고 자료:

/**

  • 클라이언트 클래스 구현
  • @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");

비록 한 줄이지만, 두 가지 세부 사항이 숨겨져 있습니다:

  1. 클라이언트는 서비스 인터페이스 Service 를 알아야 합니다. 여기서는 로컬에서 같은 package下に 있으므로 신경 쓸 필요 없지만, 실제 응용에서는 Client 와 Server 가 분리되어 있으므로, Client 는 Service 인터페이스의 사본을入手해야 합니다.否则无法调用
  2. 클라이언트는 서버의 IP 와 포트 번호를 알아야 합니다 (통신이니까, 이것이 없으면 안 됩니다)

半天忙しくしましたが, 실행 결과를 봅시다 (먼저 Server 를 실행하고, 다음으로 Client 를 실행):

P.S.Java 가 지원하는 RMI 를 이용하여 원격 프록시를 구현하는 부분은,다른 사람의 블로그 글을 참고했습니다.里面解释的更详细一些

P.S.명령줄 방식으로 RMI 등록 서비스를 시작하는 것은, 너무 번거롭고, 게다가 먼저 Stub 클래스를 생성해야 하므로, 이 방식은 권장하지 않습니다. 구체적인 조작 세부 사항은, 위의 링크 블로그 글에도 자세히 소개되어 있으므로, 여기서는 반복하지 않습니다

주의: 테스트 결과를 눈으로 볼 때까지,我们始终没有自定义 Stub 与 Skeleton 类,不是吗?对,没错,它们确实存在,只是被 RMI 隐藏起来了(据说 Java1.5 之后 RMI 中没有了 Skeleton,甚至更高的版本中连 Stub 都没有了。。不过这都没关系,我们已经清楚了最原始的远程代理)

五。요약

돌아가서 원격 프록시가 무엇을 했는지 생각해 봅시다:

  1. 메서드 호출을 인터셉트하고 제어합니다 (이것도 프록시 패턴의 가장 큰 특징입니다. 가장 전형적인 것은, 방화벽 프록시..)
  2. 원격 객체의 존재는 클라이언트에게 투명합니다 (클라이언트는 Stub 프록시 객체를 완전히 원격 객체로 생각합니다.虽然客户有点好奇为什么可能会出现异常..)
  3. 원격 프록시는 통신 세부 사항을 숨깁니다

다른 머신 (JVM) 위의 지정된 객체의 메서드를 호출해야 할 때, 원격 프록시를 사용하는 것은 좋은 선택입니다..

댓글

아직 댓글이 없습니다

댓글 작성