Ch.11 프록시 패턴

객체 접근 제어하기

About

Ch.10 상태 패턴에서의 뽑기 기계를 이어서 예제로 사용한다.

뽑기 기계의 재고와 현재 상태를 원격으로 모니터링하는 기능이 추가 요구사항으로 들어왔다.

원격 프록시

원격 프록시는 원격 객체로컬 대변자 역할을 한다.

  • 원격 객체란 다른 JVM의 힙에서 살고 있는 객체(다른 주소 공간에서 돌아가고 있는 객체)를 뜻한다.

  • 로컬 대변자는 어떤 메소드를 호출했을 때, 다른 원격 객체에게 그 메소드 호출을 전달해주는 객체를 뜻한다.

예제의 경우엔 원격 객체가 뽑기 기계(GumballMachine)가 되고, 로컬 대변자는 로컬 힙에 있는 GumballMachine의 프록시 객체가 된다.

클라이언트 객체는 원격 객체의 메소드 호출을 하는 것처럼 행동을 흉내내지만, 실제로는 로컬 힙에 들어있는 '프록시 객체'의 메소드를 호출한다.

  • 네트워크 통신과 관련된 저수준 작업은 이 프록시 객체에서 처리해 준다.

모니터링 코드에 원격 프록시 추가하기

그러면 다른 JVM에 들어 있는 객체의 메소드를 호출하는 프록시를 어떻게 만들어야 할까?

자바의 원격 메소드 호출(RMI, Remote Method Invocation)을 쓰면 된다. RMI를 이용하면 원격 JVM에 있는 객체를 찾아 그 메소드를 호출할 수 있다.

원격 메소드 기초

로컬 객체의 메소드를 호출하면 그 요청을 원격 객체에 전달해주는 시스템을 디자인한다고 생각해보자.

우선, 우리 대신 통신을 처리해 주는 보조 객체가 필요하다.

  • 클라이언트는 이 보조 객체를 이용해 로컬 객체의 메소드만 호출하면 된다. 즉, 클라이언트 보조 객체(client helper)의 메소드를 호출하는 것이다.

  • 이 보조 객체는 사실 진짜 원격 서비스는 아니다. 하지만 서버와 통신해 서버로부터 리턴되는 정보를 이용한다.

서버는 서비스 보조 객체(service helper)가 있어서, Socket 연결로 클라이언트 보조 객체로부터 요청을 받아 오고, 호출 정보를 해석해서 진짜 서비스 객체에 있는 진짜 메소드를 호출한다. 따라서 서비스 객체는 그 메소드 호출이 원격 클라이언트가 아닌 로컬 객체로부터 들어온다고 생각할 수 있는 것이다.

서비스 보조 객체는 서비스로부터 리턴값을 받아 Socket의 출력 스트림으로 클라이언트 보조 객체에게 전송한다. 클라이언트 보조 객체는 이 정보를 해석해 클라이언트 객체에 리턴한다.

자바 RMI

참고로 RMI에서 클라이언트 보조 객체는 스텁(stub), 서비스 보조 객체는 스켈레톤(skeleton)이라고 부른다.

원격 서비스 만들기 4단계

  1. 원격 인터페이스 만들기 (MyService.java)

    • java.rmi.Remote를 확장한다. 표식용 인터페이스이므로 메소드가 없지만, RMI에서 Remote는 특별한 의미를 가지므로 반드시 확장해야 한다.

    • 모든 메서드를 RemoteException을 던지도록 선언한다(throws RemoteException).

    • 원격 메서드의 인자와 리턴값은 네트워크로 전달하며 직렬화되어 포장되므로 반드시 원시 형식(primitive), 또는 직접 만든 클래스라면 Serializable 형식으로 선언한다.

  2. 서비스 구현 클래스 만들기 (MyServiceImpl.java)

    • 앞에서 만든 원격 인터페이스를 구현한다.

    • java.rmi.server.UnicastRemoteObject를 확장한다. 원격 서비스 객체 역할을 하려면 객체에 '원격 객체' 기능을 추가해야 하는데, 가장 간단한 방법이 UnicastRemoteObject를 확장하는 것이다. (Serializable을 구현하므로 serialVersionUID 필드를 추가해야 한다.)

    • RemoteException을 던지는 생성자를 구현한다.

    • 서비스를 RMI 레지스트리에 등록한다. java.rmi.Naming 클래스의 rebind() 정적 메서드에 매개변수 이름과 서비스 구현체를 넣어주면 된다.

  3. RMI 레지스트리(rmiregistry) 실행하기

    • rmiregistry는 전화번호부와 비슷하다. 클라이언트는 이 레지스트리로부터 프록시(스텁)를 받아 간다.

    • 터미널에서 rmiregistry 를 실행하면 된다.

  4. 원격 서비스 실행하기

    • 서비스 객체를 실행해야 한다. 서비스를 구현한 클래스에서 서비스의 인스턴스를 만들고 그 인스턴스를 RMI 레지스트리에 등록한다. 이렇게 하면 해당 서비스를 클라이언트에서 사용할 수 있다.

    • rmiregistry를 열었던 것과 다른 터미널을 열고 java MyRemoteImpl과 같이 서비스를 실행하면 된다.

주의사항

  • 원격 서비스를 돌리기 전에 rmiregistry를 실행해야 한다.

  • 인자와 리턴 형식은 직렬화할 수 있어야 한다. (컴파일 단계에서는 잡을 수 없다는 게 흠.)

Proxy Pattern

프록시 패턴(Proxy Pattern)은 특정 객체로의 접근을 제어하는 대리인(특정 객체를 대변하는 객체)을 제공한다.

프록시 객체는 다른 객체의 '대리인'이라고 봐도 무방하다.

프록시에서 접근을 제어하는 방법은:

  • 원격 프록시를 써서 원격 객체로의 접근을 제어할 수 있다.

  • 가상 프록시(virtual proxy)를 써서 생성하기 힘든 자원으로의 접근을 제어할 수 있다.

  • 보호 프록시(protection proxy)를 써서 접근 권한이 필요한 자원으로의 접근을 제어할 수 있다.

  • RealSubjectSubject 인터페이스는 같은 인터페이스를 구현한다. 그래서 RealSubject 자리에 Proxy를 넣을 수 있다.

  • 진짜 작업은 RealSubject 객체가 처리하고, Proxy는 이 객체의 대변인 역할을 하며 객체로의 접근을 제어한다.

  • Proxy에는 RealSubject의 레퍼런스가 들어 있다.

원격 프록시 v.s. 가상 프록시

  • 원격 프록시는 다른 JVM에 들어있는 객체의 대리인에 해당하는 로컬 객체이다. 프록시의 메서드를 호출하면 호출이 네트워크로 전달되어 원격 객체의 메서드가 호출된다. 그리고 결과는 다시 프록시를 거쳐 클라이언트에게 전달된다.

  • 가상 프록시는 생성하는 데 많은 비용이 드는 객체를 대신한다. 진짜 객체가 필요한 상황이 오기 전까지 객체의 생성을 미루는 기능을 제공한다(Lazy Loading - e.g., JPA Entity Proxy). 객체 생성 전이나 도중에 객체를 대신하기도 한다. 생성이 끝나면 RealSubject에 직접 요청을 전달한다.

원격 프록시의 또 다른 예시

앞서 본 뽑기 기계 모니터링 기기 말고 다른 예시를 들자면 앨범 커버 가상 프록시를 만들 수 있다. 네트워크 URL로부터 이미지를 불러오며, 완전히 불러올 때까지 앨범 커버를 불러온다는 메시지를 대신 화면에 표시한다.

다른 네트워크에 있는 데이터를 로딩하고 있을 때 스켈레톤을 대신 보여줄 수 있는 것이다.

보호 프록시 만들기

자바의 java.lang.reflect 패키지 안에 프록시 기능이 내장되어 있다. 이 패키지로 즉석에서 하나 이상의 인터페이스를 구현해 지정한 클래스에 메소드 호출을 전달하는 프록시 클래스를 만들 수 있다. 진짜 프록시는 실행 중에 생성되므로 이러한 기술을 동적 프록시(dynamic proxy)라고 부른다.

프록시 패턴의 응용 방식 중 하나인 보호 프록시(Protection Proxy)는 접근 권한을 바탕으로 객체로의 접근을 제어한다.

  • 예를 들어, 회사 직원을 나타내는 객체가 있다면 일반 직원 객체와 관리자 객체가 호출할 수 있는 메서드의 범위가 다를 것이다.

다양한 변종 프록시

  • 방화벽 프록시(Firewall Proxy)는 일련의 네트워크 자원으로의 접근을 제어함으로써 주제를 나쁜 클라이언트로부터 보호해 준다. (e.g., 기업용 방화벽)

  • 스마트 레퍼런스 프록시(Smart Reference Proxy)는 주제가 참조될 때마다 추가 행동을 제공한다. 객체의 레퍼런스 개수를 센다던가 하는 식이다.

  • 캐싱 프록시(Caching Proxy)는 비용이 많이 드는 작업의 결과를 임시로 저장해 준다. 여러 클라이언트에서 결과를 공유하게 함으로 써 계산 시간과 네트워크 지연을 줄여 주는 효과가 있다. (e.g., 웹 서버 프록시 또는 컨텐츠 관리 및 퍼블리싱 시스템)

  • 동기화 프록시(Synchronization Proxy)는 여러 스레드에서 주제에 접근할 때 안전하게 작업을 처리할 수 있게 해준다. (e.g., 분산 환경에서 일련의 객체로의 동기화된 접근을 제어해 주는 자바 스페이스)

  • 복잡도 숨김 프록시(Complexity Hiding Proxy, a.k.a. 퍼사드 프록시 Facad Proxy)는 복잡한 클래스의 집합으로의 접근을 제어하고, 그 복잡도를 숨겨 준다. 이 프록시와 퍼사드 패턴의 차이점은 프록시는 접근을 제어하지만 퍼사드 패턴은 대체 인터페이스만 제공한다는 점에 있다.

  • 지연 복사 프록시(Copy-on-Write Proxy)는 클라이언트에서 필요로 할 때까지 객체가 복사되는 것을 지연시킴으로써 객체의 복사를 제어한다. 변형된 가상 프록시라고 할 수 있다. (e.g., 자바의 CopyOnWriteArrayList)

Last updated