프록시 패턴
정의
- 어떤 객체에 대한 접근을 제어하기 위한 용도로 대리인이나 대변인에 해당하는 객체를 제공하는 패턴
- 다른 객체를 대변하는 객체를 만들어서 주 객체에 대한 접근을 제어할 수 있다.
프록시 패턴을 이용하면 원격 객체라든가 생성하기 힘든 객체, 보안이 중요한 객체와 같은 다른 객체에 대한 접근을 제어하는 대변자 객체를 만들수 있다.
프록시에 접근을 제어하는 몇가지 방법
- 원격 프록시를 써서 원격 객체에 대한 접근을 제어할 수 있다.
- 가상 프록시를 써서 생성하기 힘든 자원에 대한 접근을 제어할 수 있다.
- 보호 프록시를 써서 접근 권한이 필요한 자원에 대한 접근을 제어할 수 있다.
원격 프록시의 역할
원격객체에 대한 로컬 대변자 역할을 한다.
원격 객체(remote object)
- 다른 자바 가상 머신의 힙에서 살고 있는 객체(조금 더 일반적으로 얘기하자면, 다른 주소 공간에서 돌아가고 있는 원격 캐시)를 뜻한다.
로컬 대변자(local representative)
- 로컬 대변자의 어떤 메서드를 호출하면 다른 원격 객체한테 그 메서드를 호출을 전달해주는 역할을 맡고 있는 객체를 로컬 대변자라고 생각하면된다.
클라이언트 객체에서 원격 객체의 메서드 호출을 하는 것처럼 행동한다.
하지만 실제로는 로컬 힙에 들어있는 '프록시' 객체의 메서드를 호출하고 있다.
네트워크 통신과 관련된 저수준 작업은 이 프록시 객체에서 처리해준다.
원격프록시 추가
Duck d = <다른힙에 있는 객체>
d라는 변수가 어떤 객체를 참조하든, 그 객체는 그 선언문이 들어있는 코드와 같은 힙 공간에 들어있어야만 한다.
자바의 원격 메서드 호출(RMI, Remote Method Invocation)이 쓰인다.
RMI를 이용하면 원격 JVM에 있는 객체를 찾아서 그 메서드를 호출할 수 있다.
원격 메서드의 기초
로컬 객체에 대해서 메서드를 호출하면, 그 요청을 원격 객체에 전달할 수 있게 해주는 시스템을 디자인해야 한다고 생각해보자.
통신을 처리해주는 보조 객체가 필요할 것이다.
보조 객체를 이용하면 클라이언트 입장에서 로컬 객체에 대해서만 메서드를 호출하면된다.
클라이언트 보조 객체(Client helper)의 메서드를 호출하는 것이다.
클라이언트 입장에서는 그 보조 객체가 실제 서비스를 제공한다고 생각하는 것이다. 그러면 클라이언트 보조 객체가 그 요청을 원격 객체한테 전달한다.
즉, 클라이언트 객체는 원격 서비스에 있는 메서드를 호출한다고 생각하고 작업을 처리한다.
클라이언트 보조 객체가 서비스 객체인 양 행동하기 때문이다. 클라이언트에서 호출하고자 하는 메서드가 들어있는 객체인척 하기 때문이다.
하지만 클라이언트 보조 객체는 진짜 원격 서비스가 아니다.(진짜 서비스랑 같은 메서드가 들어있기 때문에) 진짜 원격 서비스인 것 같아 보이긴 하지만, 클라이언트에서 필요로하는 실제 메서드 로직이 그 안에 들어있는 것은 아니다. 클라이언트 보조 객체에서는 서버에 연락을 취하고, 메서드 호출에 대한 정보(메서드 이름, 인자 등)을 전달하고, 서버로부터 리턴되는 정보를 기다린다.
서버쪽에는 서비스 보조 객체(service helper)가 있어서,(Socket 연결을 통해서) 클라이언트 보조 객체로부터 요청을 받아오고, 호출에 대한 정보를 해석해서 진짜 서비스 객체에 있는 진짜 메서드를 호출한다.
따라서 서비스 객체 입장에서는 그 메서드 호출이 원격 클라이언트가 아닌 로컬 객체로부터 들어오는 셈이다.
서비스 보조 객체는 서비스로부터 리턴값을 받아서 잘 포장해서(Socket의 출력 스트림을 통해서) 클라이언트 보조 객체한테 전송한다.
클라이언트 보조 객체에서는 그 정보를 해석해서 리턴값을 클라이언트 객체한테 리턴한다.
자바의 RMI의 개요
RMI에서 우리 대신 클라이언트와 서비스 보조 객체를 만들어준다.
보조 객체에는 원격 서비스와 똑같은 메서드가 들어있다.
RMI를 이용하면 우리가 직접 네트워킹 및 입출력 관련 코드를 직접작성하지 않아도 된다.
클라이언트에서는 그냥 그 클라이언트랑 같은 로컬 JVM에 있는 메서드를 호출하듯이 원격 메서드(진짜 서비스 객체에 있는 메서드)를 호출할 수 있다.
또한 클라이언트에서 원격 객체를 찾아서 그 원격 객체에 접근하기 위해 쓸수 있는 룩업(lookup) 서비스와 같ㅇ은 것도 RMI에서 제공해준다.
RMI 호출과 일반적인 로컬메서드 호출의 차이점도 있다.
클라이언트 입장에서는 로커 ㄹ메서드 호출과 똑같은 식으로 메서드를 호출하면되지만, 실제로 클라이언트 보조 객체에서 네트워크를 통해서 호출을 전송한다는 점이다.
따라서 네트워킹 및 입출력 기능이 활용될수 밖에 없다.
네트워킹이나 입출력 기능을 쓸때는 위험이 따르다는 것을 기억해야한다.
그 과정에서 문제가 생길수 있다. 그렇기 때문에 어드서든 예외가 발생할 수 있다.
결과적으로 클라이언트에서도 예외에 대비하고 있어야 한다.
RMI용어
-RMI에서 클라이언트 보조 객체는 스터브(stub), 서비스 보조객체는 스켈레톤(skeleton)이라고 부른다.
원격 서비스만들기
1. 원격 인터페이스 만들기
- 원격 인터페이스에서는 클라이언트에서 원격으로 호출할 수 있는 메서드를 정의한다.
클라이언트에서 이 인터페이스를 서비스의 클래스 형식으로 사용한다. 스터브와 실제 서비스에서 모두 이 인터페이스를 구현해야 한다.
2. 서비스 구현 클래스 만들기
- 실제 작업을 처리하는 클래스이다. 원격 인터페이스에서 정의한 원격 메서드를 실제로 구현한 코드가 들어있는 부분이다. 나중에 클라이언트에서 이 객체에 있는 메서드를 호출하게 된다.
3. rmic를 이용하여 스터브와 스켈레톤 만들기
- 클라이언트 및 서비스 '보조객체'를 생성한다.
직접 클래스들을 만들거나, 클래스를 만들기 위한 소스 코드를 건드릴 필요는 없다.
JDK에 포함되어있는 rmic 툴을 실행시키면 자동으로 만들어진다.
4. RMI 레지스트리(rmiregistry)를 실행시킨다.
-rmiregistry는 전화번호부랑 비슷하다.
클라이언트에서는 이 레지스트리로부터 프록시(클라이언트 스터브)를 받아갈수 있다.
5. 원격 서비스 시작
- 서비스 객체를 가동시켜야 한다.
서비스를 구현한 클래스에서 서비스의 인스턴스를 만들고 그 인스턴스를 RMI 레지스트리에 등록한다.
일단 RMI 레시즈트리에 등록되고 나면 클라이언트에서 그 서비스를 사용할 수 있다.
원격 인터페이스 만들기
1) java.rmi.Remote를 확장
인터페이스를 확장해서 다른 인터페이스를 만들수 있다.
public interface MyRemote extends Remote {
public String sayHello() throws RemoteException;
}
2) 모든 메서드를 RemoteException을 던지는 메서드를 선언
스터브에서는 네트워킹 및 각종 입출력 작업을 처리해야 하기 때문에 예외처리 대비를 해야만 한다.
인터페이스를 정의할때 모든 메서드에서 예외를 선언하면, 그 형식의 레퍼런스에 대해서 메서드를 호출하는 코드에서는 반드시 그 예외를 처리하거나 선언해야한다.
3) 인자와 리턴값은 반드시 원시형식(primitive) 또는 Serializable 형식으로 선언
원격 메서드의 인자는 모두 네트워크를 통해서 전달되어야하며, 직렬화를 통해서 포장된다.
리턴값도 마찬가지이다.
원시형식이나 String, 또는 API에서 많이 쓰이는 일반적인 형식(배열, 컬렉션등)을 사용한다면 별로 걱정하지 않아도 된다. 혹시 직접 만든 형식을 전달한다면 클래스를 만들때 Serializable 인터페이스를 구현해야 한다.
서비스 구현 클래스 만들기
package patterns.proxy.remote;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote {
@Override
public String sayHello() throws RemoteException {
return "Server says, 'Hey' 자리 비우고 어디 가신겁니까?";
}
public MyRemoteImpl() throws RemoteException {
MyRemote service = new MyRemoteImpl();
try {
Naming.rebind("RemoteHello", service);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
1) 원격 인터페이스를 구현한다.
서비스 클래스에서는 반드시 원격 인터페이스를 구현해야한다.
클라이언트에서 그 인터페이스에 있는 메서드를 호출한다.
2) UnicastRemoteObject를 확장한다.
원격 서비스 객체 역할을 하려면 객체에 '원격 객체가 되기 위한'기능을 추가해야한다.
가장 간단한 방법은 java.rmi.server 패키지에 들어있는 UnicastRemoteObject를 확장해서 그 수퍼 클래스에서 제공하는 기능을 활용하는 방법이다.
3) RemoteException을 선언하는 인자가 없는 생성자 만들기
수퍼클래스인 UnicastRemoteObject에는 한가지 문제점이 있다.
생성자에서 RemoteException을 던진다.
이문제를 해결하려면 서비스를 구현하는 클래스에서도 RemoteException을 선언하는 생성자를 만들어야 한다.
어떤 클래스가 생성될때 그 수퍼클래스의 생성자도 반드시 호출된다. 따라서 수퍼클래스 생성자에서 어떤 예외를 던진다면 서브클래스의 생성자에서도 그 예외를 선언해야 된다.
4) 서비스를 RMI 레지스트리에 등록
원격 서비스가 완성되고 나면 원격 클라이언트에서 쓸수 있게 해줘야 된다.
방법은 인스턴스를 만든 다음 RMI레지스트리에 등록하기만 하면된다.
(이 클래스가 실행될때 RMI 레지스트리가 돌아가고 있어야한다.)
서비스를 구현한 객체를 등록하면 RMI 시스템에서는 스터브만 레지스트리에 등록한다.
클라이언트에서는 스터브만 필요하다. 서비스를 등록할 때는 java.rmi.Naming 클래스에 있는 rebind() 라는 정적 메서드를 사용하면된다.
스터브 및 스켈레톤 생성
1) 서비스를 구현한 클래스에 대해서 rmic를 돌린다.
JDK에 포함되어있는 rmic 툴은 서비스를 구현한 클래스를 받아서 스터브와 스켈레톤, 이렇게 두개의 새로운 클래스를 만들어준다.
스터브와 스켈레톤 클래스에는 원래 클래스의 이름에 각각 _Stub와 _Skel이 추가된 이름이 붙는다.
rmic에는 그외에도 스켈레톤은 생성하지 않는 옵션이라든가 스터브와 스켈레톤의 소스 코드를 보여주는 옵션, IIOP를 프로토콜로 사용하는 옵션 등 다양한 옵션을 제공한다.
하지만 아마 대부분의 경우에 여기에 나와있는 식으로 하게될 것이다.
새로 만들어지는 클래스는 현재 디렉토리에 저장된다.
rmic를 서비스를 구현한 클래스 파일이 들어있는 디렉토리에서 실행시키고 있으므로, 세 클래스 파일이 모두 같은 디렉토리에 들어있을 것이다.(여기에서는 일부러 패키지를 사용하지 않았다. 하지만 실전에서는 패키지 디렉토리 구조와 전체 이름을 고려해야한다.)
rmiregistry 실행
1) 터미널을 새로 띄워서 rmiregistry를 실행시킨다.
클래스에 접근할수 있는 디렉토리에서 실행시켜야한다.
'classes'디렉토리에서 실행시키는 것이 가장 간단할 것이다.
서비스가동
1) 다른 터미널을 열고 서비스를 시작한다.
이 작업은 원격 서비스를 구현한 클래스의 main() 메서드를 통해서 실행시킬수도 있지만, 별도의 클래스로부터 할수도 있다.
서비스를 구현한 클래스의 main() 메서드에서 바로 객체 인스턴스를 만들고 RMI 레지스트리에 등록했다.
클라이언트에서는 스터브 객체(프록시)를 가져와야 한다.
거기에 있는 메서드를 호출해야한다.
그리고 바로 이때 RMI 레지스트리가 활약하게 된다.
클라이언트에서 룩업(lookup)을 통해 스터브 객체를 요청한다.
이름을 건네주면서 그 이름에 맞는 스터브 객체를 요구한다.
스터브 객체를 룩업하고 가져오는데 필요한 코드
MyRemote service = (MyRemote) Naming.lookup("rmi://127.0.0.1/RemoteHello");
작동방식
1) 클라이언트에서 RMI 레지스트리를 룩업한다.
Naming.lookup("rmi://127.0.0.1/RemoteHello");
2) RMI 레지스트리에서 스터브 객체를 리턴한다.
스터브 객체는 lookup() 메서드의 리턴ㄱ밧으로 전달되며, RMI에서는 그 스터브를 자동으로 역직렬화한다.
이때 (rmic에서 생성해준) 스터브 클래스가 반드시 클라이언트 쪽에 있어야만 한다.
그 클래스가 없으면 역직렬화를 할 수 없다.
3)클라이언트에서 스터브에 대해 메서드를 호출한다.
스터브가 진짜 서비스 객체라고 생각해도 된다.
클라이언트에서 스터브 클래스를 가져오는 방법
- 클라이언트에서 룩업을 하려면 어떤식으로든(rmic를 써서 만들어낸) 스터브 클래스를 보유하고 있어야 한다.
클래스가 없이는 스터브를 가져와도 역직렬화 할수가 없고, 그러면 원격 메서드 호출을 할수가 없다.
그리고 원격 객체를 호출한 결과로 리턴되는 직렬화된 객체의 클래스도 있어야 한다.
간단한 시스템에서는 그냥 클래스를 직접 클라이언트로 복사해도 된다.
하지만 직접 클래스 파일을 복사하는 것보다 훨씬 좋은 방법으로 '동적클래스 다운로딩(dynamic class downloading)이라는 방법이다.
동적 클래스 다운로딩을 사용하면(스터브 같은) 직렬화된 객체에 그 객체의 클래스 파일이 있는 위치를 나타내는 URL이 내장된다. 그리고 역직렬화 과정에서 로컬 시스템에서 클래스 파일을 찾지 못하게 되면 내장된 URL로부터 HTTP GET요청을 통해 클래스 파일을 가져온다.
따라서 클래스 파일을 보내줄수 있는 웹서버가 있어야하며, 클라이언트의 보안 매개변수를 변경해야 할수도 있다.
스터브 객체에 대해서만 얘기해 보자면, 클라이언트에서 클래스 파일을 다른 방식으로 받는 것도 가능하다.
RMI를 사용시 실수하는것
1) 원격 서비스를 돌리기전에 rmiregistry를 실행시키지 않는 경우( Naming.rebind()를 호출해서 서비스를 등록하는 시점에서 RMI 레지스트리가 돌아가고 있어야 한다)
2) 인자와 리턴 형식이 직렬화 가능한지 확인하지 않는 경우(실행시켜보기전까지 오류를 알수가 없다. 컴파일러에서는 잡아낼수 없는 문제)
3) 클라이언트에 스터브 클래스를 건네주지 않는 경우
transient 키워드
JVM에서는 그 필드를 직렬화하지 않는다.
하지만 객체를 직렬화해서 전송받은 후에 이 필드를 호출하게 되면 안좋을일이 생길수 있다.
프록시에 있는 메서드를 호출하면 네트워크를 통해서 메서드 호출이 전달되며, 그 결과로 Strin, 정수, State 객체가 리턴된다.
프록시를 사용하고 있기 때문에 GumballMonitor에서는 원격 호출을 하고 있다는 사실조차 알 필요가 없다.
물론 RemoteException에 대한 대비는 있어야된다.
1) CEO가 모니터링을 시작하면 GumballMonitor에서는 우선 뽑기 기계 원격 객체에 대한 프록시를 가져온다음 getState()와 getCount(), getLocation()을 호출한다.
1. GumballMonitor -> lookup("seattle")
2. RMI레지스트리 -> 프록시리턴
3. GumballMonitor -> getState() 호출
2) 프록시의 getState()가 호출되면 프록시에서는 그 호출을 원격 서비스로 전달한다. 스켈레톤에서 그 요청을 받아서 뽑기 기계한테 전달한다.
1.프록시 -> 스켈레톤 getState()
3) GumballMachine에서 스켈레톤에 상태를 리턴한다. 그러면 스켈레톤에서는 리턴값을 직렬화 한 다음 네트워크를 통해서 프록시한테 전달한다. 프록시에서는 리턴값을 역직렬화해서 GumballMachine에게 리턴한다.
1. GumballMachine -> State객체 스텔레톤
2. 스켈레톤 직렬화된 State객체 -> 프록시
3. 프록시 -> GumballMonitor State객체
GumballMonitor는 RemoteException이 발생할 가능성에 대비하는 부분을 제외하면 바뀌는게 없다.
구상 클래스 대신 GumballMachineRemote 인터페이스를 사용하는 것도 바뀐부분
GumballMachine도 다른 인터페이스를 구현하고 생성자에서 RemoteException을 던지는 것을 선언해야 하는 점을 제외하면 거의 바뀌지 않음
RMI 레지스트리를 써서 스터브를 등록하거나 룩업하는 코드도 추가해야 한다.
인터넷을 통해 뭔가 하려면 그런 코드가 반드시 필요할수밖에 없다.
QnA
자바1.2부터는 RMI 런타임에서 클라이언트 호출을 리플렉션을 이용하여 직접 원격 서비스로 넘길수 있게 되었다.
여기에서는 클라이언트 스터브와 원격 서비스 사이의 통신을 가능하게 해주는 메커니즘을 이애하는데 도움이 될수 있기 때문에 스켈레톤을 보여줌
자바 5에서 RMI와 동적 프록시가 결합되어서 스터브마저도 동적 프록시를 통해서 동적으로 생성.
원격 객체의 스터브는 java.lang.reflect.Proxy 인스턴스(호출 핸들러 포함)으로 자동으로 만들어진다.
클라이언트에 의한 로컬 호출을 원격 객체한테 전달해주는 역할을 한다.
따라서 rmic를 전혀 쓰지 않아도 된다.
원격 객체의 메서드를 호출하고 그 결과를 리턴받는 작업이 전부 자동으로 처리된다.
#원격 프록시
#가상 프록시
가상 프록시는 생성하는데 많은 비용이 드는 객체를 대신하는 역할을 맡는다.
실제로 진짜 객체가 필요하게 되기 전까지 객체의 생성을 미루게 해주는 기능을 제공하기도 한다.
객체 생성전, 또는 객체 생성 도중에 객체를 대신하기도 한다.
객체 생성이 완료되고 나면 그냥 RealSubject에 요청을 직접 전달해준다.
클라이언트-> Proxy -> RealSubject
ImageProxy 작동방법
1. ImageProxy에서 우선 ImageIcon을 생성하고 네트워크 URL로부터 이미지를 불러온다.
2.이미지를 가져오는 동안에는 로딩 커피 대기중이라는 메시지를 화면에 표시한다.
3. 이미지 로딩이 끝나면 paintIcon(), getWidth(), getHeight() 를 비롯한 모든 메서드 호출을 이미지 아이콘 객체한테 넘긴다.
4. 사용자가 새로운 이미지를 요청하면 프록시를 새로 만들고 위의 과정을 새로 진행한다.
package patterns.proxy.Image;
import java.awt.*;
public interface Icon {
public int getIconWidth();
public int getIconHeight();
public void paintIcon(final Component c, Graphics g, int x, int y);
}
package patterns.proxy.Image;
import javax.swing.*;
import java.awt.*;
import java.net.URL;
public class ImageProxy implements Icon {
ImageIcon imageIcon; //이미지 로딩이 끝났을대 실제 이미지를 화면에 표시하기 위한 진짜 아이콘 객체
URL imageURL;
Thread retrievalThread;
boolean retieving = false;
public ImageProxy(URL imageURL) {
this.imageURL = imageURL;
}
//imageicon 로딩이 끝날기 전까지는 기본 너비 및 기본 높이
@Override
public int getIconWidth() {
if (imageIcon != null) {
return imageIcon.getIconWidth();
} else {
return 800;
}
}
@Override
public int getIconHeight() {
if (imageIcon != null) {
return imageIcon.getIconHeight();
} else {
return 600;
}
}
//imageicon에 메서드 호출을 전달하여 아이콘을 화면에 표시해준다.
//하지만 생성되지 않았으면 이미지를 가져오고 있는 중이라는 메세지 표시
//아이콘을 화면에 표시할때 이 메서드를 호출하면된다.
@Override
public void paintIcon(final Component c, Graphics g, int x, int y) {
if (imageIcon != null) {
imageIcon.paintIcon(c, g, x, y);
} else {
g.drawString("Loading CD cover, please wait...", x+300, y+190);
//이미지를 가져오고 있는 중이 아니면
if (!retieving) {
//이미지 로딩 작업 시작 repaint() 메서드는 한 스레드에서만 호출하기 때문에 스레드 안정성을 확보되었다고 할수 있다.
retieving = true;
//사용자 인터페이스가 죽지 않도록 별도의 스레드에서 이미지를 가져온다.
retrievalThread = new Thread(new Runnable() {
@Override
public void run() {
try {
imageIcon = new ImageIcon(imageURL, "CD Cover");
//이미지가 확보되고 나면 repaint() 메서드르 호출해서 화면을 갱신한다.
c.repaint();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
retrievalThread.start();
//ImageIcon의 인스턴스가 만들어진 후에 화면을 표시하게 되면 paintIcon() 메서드가 호출되면서 로딩중이라는 메시지가 아닌
//실제 이미지가 화면에 표시된다.
}
}
}
1. 화면에 이미지 표시하기 위한 ImagProxy만들었고, paintIcon()메서드를 호출되면 ImageProxy에서 이미지를 가져오고 ImagIcon 객체를 생성하는 스레드를 시작한다.
화면 --->pinatIcon() -> ImgeProxy -> ImageIcon ---> 이미지요청(인터넷에 있는 이미지)
화면 <- <---로딩메시지표시 ImgeProxy
2. 얼아후 이미지가 리턴되고 ImagIcon의 인스턴스를 만드는 작업이 완료
ImageIcon <--- 이미지 가져옴(인터넷에 있는 이미지)
3. ImagIcon 생성이 완료된 후에 paintIcon()이 호출되면 프록시에서 그 호출을 곧바로 ImagIcon객체 한테 넘김
화면 --->pinatIcon() -> ImgeProxy --->pinatIcon() -> ImageIcon
화면 <- <--- 실제 이미지를 화면에 표시함 ImagIcon
프록시의 공통점
클라이언트에서 실제 객체의 메서드를 호출하면 그 호출을 중간에 가로챈다.
간접적으로 작업을 처리하면 요청내역을 원격 시스템에 있는 객체에 전달할 수 있고, 생성하는데 많은 비용이 드는 객체를 대변해줄수도 있고 클라이언트별로 호출할 수 있는 메서드를 제한하는 보드가드 역할을 하는 것도 가능하다.
어떤식으로 클라이언트에서 진짜 객체가 아닌 프록시를 사용하도록 만든가?
-> 진짜 객체의 인스턴스를 생성해서 리턴하는 팩토리를 사용하는 방법이다.
이 작업은 팩토리 메서드 내에서 진행되기 때문에 실제 객체를 프록시로 감싼 다음에 리턴할 수 있다.
클라이언트에서는 진짜 객체를 쓰고 있는지, 아니면 프록시 객체를 쓰고 있는지 알지 못한다.
데코레이터와 차이점
데코레이터는 클래스에서 새로운 행동을 추가하기 위한 용도로 쓰이지만 프록시는 어떤 클래스에 접근을 제어하기 위한 용도로 쓰임
ImageProxy가 ImagIcon에 대한 접근을 제어하고 있다는 것.
프록시는 클라이언트와 ImageIcon을 분리시킨다.
그 둘이 결합되어 있었다면 이미지가 완전히 로딩되기전까지는 인터페이스 전체를 화면에 표시할수 없다.
프록시가 ImageIcon에 대한 접근을 통제해주기 때문에 ImageIcon 객체 생성이 완료되기 전까지는 프록시에서 다른 내용을 화면에 표시해줄수 있었다.
ImageIcon 생성이 완료되고 나면 프록시에서 접근을 허용했기 때문에 화면에 이미지를 표시할 수 있었다.
어댑터와 차이점
모두 클라이언트와 다른 객체 사이에 끼여들어서 클라이언트로부터 요청을 받아와서 다른 객체한테 전달해주는 역할을한다.
어댑터에서는 다른 객체의 인터페이스를 바꿔주지만, 프록시에서는 똑같은 인터페이스를 사용한다는 차이점이 있다.
#보호프록시
java.lang.reflect
프록시 기능이 내장되었음.
즉석에서 한 개 이상의 인터페이스를 구현하고 메서드 호출을 지정해준 클래스에 전달할수 있는ㄴ 프록시 클래스를 만들수 있다.
실제 프록시 클래스는 실행중에 생성되기 때문에 이러한 자바 기술을 동적 프록시라고 부른다.
동적프록시를 활용해서 보호프록시
Proxy 클래스는 자바가 만들어주기 때문에 Proxy 클래스한테 무슨 일을 할지 알려주기 위한 방법이 필요하다.
Proxy 클래스를 직접 구현하지 않기 때문에 필요한 코드를 그 클래스에 집어넣을 수는 없다.
Proxy클래스에서 집어넣을 수 없다면 InvocationHandler에 집어 넣으면된다.
InvocationHandler는 프록시에 대해서 호출되는 모든 메서드에 대해 응답하는 역할을 맡는다.
Proxy에서 메서드 호출을 받으면 항상 InvocationHandler에 직접 작업을 부탁한다고 생각하면된다.
예) 결혼정보
QnA
동적 프록시에서 동적?
클래스가 실행중에 생성으로 실제 코드가 실행되기전까지는 프록시 클래스 자체가 없다.
전달해준 인터페이스를 바탕으로 즉석에서 클래스가 생성된다.
프록시 자체는 Proxy.newProxyInstance()라는 정적메서드에 의해 실행중에 동적으로 생성된다.
핵심정리
- 프록시 패턴을 이용하면 어떤 객체에 대한 대변인을 내세워서 클라이언트의 접근을 제어할 수 있다. 접근을 관리하는 방법에는 여러가지가 있다.
- 원격 프록시는 클라이언트와 원격 객체 사이의 데이터 전달을 관리해준다.
- 가상 프록시는 인스턴스를 만드는 데 많은 비용이 드는 객체에 대한 접근을 제어한다.
- 보호 프록시는 호출하는 쪽의 권한에 따라서 객체에 있는 메서드에 대한 접근을 제어한다.
- 그외에도 캐싱프록시, 동기화 프록시, 방화벽 프록시, 지연복사 프록시와 같이 다양한 변형된 프록시 패턴이 있다.
- 프록시 패턴의 구조는 데코레이터 패턴의 구조와 비슷하지만 그용도가 다르다는 차이점이 있다.
- 데코레이터 패턴에서는 객체에 행동을 추가하지만 프록시 패턴에서는 접근을 제어한다.
- 자바에 내장된 프록시 지원 기능을 이용하면 동적 프록시 클래스를 즉석에서 만들어서 원하는 핸들러에서 호출을 처리하도록 할 수 있다.
- 다른 래퍼를 쓸대와 마찬가지로 프록시를 쓰면 디자인에 포함되는 클래스와 객체의 수가 늘어난다.
출처 - Head First Design Patterns 저자- 에릭 프리먼, 엘리자베스 프리먼, 케이시 시에라, 버트 베이츠
'JAVA > Design Patterns' 카테고리의 다른 글
13. 디자인패턴 정리 (0) | 2019.01.02 |
---|---|
12. 컴파운드 패턴 (0) | 2019.01.02 |
10.스테이트 패턴 (0) | 2018.12.17 |
9.컴포지트 패턴 (0) | 2018.12.13 |
9.이터레이터 패턴 (0) | 2018.12.13 |