시작하기 전 ...
프록시 패턴은 목적에 따라 여러가지 패턴의 양상을 띈다. 이번 포스팅에서는 원격 프록시에 대해 알아보도록 하고,
전반적인 프록시 패턴의 개념은 아래 포스팅을 참고하면 된다.
https://dev-rosiepoise.tistory.com/80
[Proxy Pattern] 프록시 패턴이란?
Goal 프록시 패턴이란 무엇인지 알아보고 이해한다 프록시 패턴의 다이어그램 프록시 패턴 (Proxy Pattern) 특정 객체로의 접근을 제어하는 대리인(특정 객체를 대변하는 객체)를 제공한다. 실제 객
dev-rosiepoise.tistory.com
Goal
- 원격 프록시에 대해 알아보고 이해한다
- RMI에 대해 알아본다
- 원격 프록시 샘플 코드를 통해 원격 프록시를 이해한다
프록시 패턴 (Proxy Pattern)
특정 객체로의 접근을 제어하는 대리인(특정 객체를 대변하는 객체)를 제공한다.
원격 프록시 (Remote Proxy)
원격 프록시의 역할
- 원격 객체
- 로컬 대변자 역할을 한다. 원격 객체란, JVM의 heap영역에 살고 있는 객체를 뜻한다.
- 로컬 대변자
- 어떤 메소드를 호출하면 다른 원격 객체에서 그 메소드 호출을 전달해주는 객체를 로컬 대변자라고 한다.
- 클라이언트 객체
- 프록시를 활용하는 객체로, 원격 객체의 메소드를 호출하는 것처럼 행동한다. 하지만 실제로는 로컬 힙에 들어있는 '프록시'객체의 메소드를 호출하며, 네트워크 통신과 관련된 저수준 작업은 이 프록시 객체에서 처리한다.
원격 프록시를 사용하는 이유
원격 객체에 대한 접근 제어가 가능하게 하여 서로 다른 주소 공간에 있는 객체에 대해 마치 같은 주소 공간에 있는 것처럼 동작하게 만들기 때문이다
자바 RMI (Remote Method Invocation) 란?
Duck d = <다른 힙에 있는 객체>
변수 d가 어떤 객체를 참조하든, 그 객체는 선언문이 들어있는 코드와 같은 힙에 존재해야 하기 때문에 바로 그점에서 자바의 원격 메소드 호출(Remote Method Invocation)이 사용된다. RMI를 사용하면 원격 JVM에 있는 객체를 찾아 그 메소드를 호출할 수 있다.
RMI의 역할
- 우리 대신 클라이언트와 서비스 보조 객체를 만들어준다.
- RMI를 사용하면 네트워킹 및 입출력 관련 코드를 직접 작성하지 않아도 됨
- 클라이언트가 원격 객체를 찾아 접근할 때 쓸 수 있는 룩업(lookup) 서비스 제공
원격 서비스를 만드는 4단계
1. 원격 인터페이스 생성 - MyRemote Interface
이는 클라이언트가 원격으로 호출할 메소드를 정의한다. 클라이언트에서 이 인터페이스를 서비스의 클래스 형식으로 사용한다. 스텁과 실제 서비스에 이 인터페이스를 구현해야 한다.
public interface MyRemote extends Remote {
String sayHello() throws RemoteException;
}
- java.rmi.Remote를 확장
- 이 인터페이스에서 원격 호출을 지원하는 것을 알려줌
- 모든 메소드를 RemoteException을 던지도록 함
- 모든 메소드에서 RemoteException을 선언하면 클라이언트에서 예외 상황 발생 대비 가능
- 원격 메소드의 인자와 리턴값은 반드시 원시 형식 또는 Serializable 형식으로 선언
- 원격 메소드의 인자는 모두 네트워크로 전달되어야 하며, 직렬화로 포장된다. 또, 서버에서 클라이언트로 다시 전송해야 하므로 이 리턴값을 직렬화 할 수 있어야하기 때문이다.
2. 서비스 구현 클래스 생성 - MyRemoteImpl
public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote {
private static final long serialVersionUID = 1L;
@Override
public String sayHello() {
return "server says, hey";
}
public MyRemoteImpl() throws RemoteException {
}
public static void main(String[] args) {
try{
MyRemote service = new MyRemoteImpl();
Naming.rebind("remoteHello", service);
}catch (Exception e) {
e.printStackTrace();
}
}
}
- 서비스 클래스에 원격 인터페이스를 구현
- 클라이언트가 인터페이스의 메소드를 호출하기 때문 !
- UnicastRemoteObject를 확장
- 원격 서비스 객체 역할을 하기 위해 원격 객체 기능을 추가하는 것
- 그 방법으로 java.rmi.server 패키지에 있는 UnicastRemoteObject를 확장해서 슈퍼클래스에서 제공하는 기능으로 처리
- UnicastRemoteObject는 Serializable을 구현하므로 serialVersionUUID 필드가 필요
- RemoteException을 선언하는 생성자를 구현
- 슈퍼클래스 UnicastRemoteObject에는 생성자가 RemoteException을 던지는 문제를 해결하기 위해 생성자를 만듦
- 어떤 클래스가 생성될 때 그 슈퍼클래스의 생성자도 반드시 호출되므로, 슈퍼클래스 생성자가 어떤 예외를 던진다면 서브클래스의 생성자도 예외를 선언해야함
- 서비스를 RMI 레지스터리에 등록
- Naming.rebind("remoteHello", service);
- 원격 서비스를 원격 클라이언트에서 쓸 수 있도록 만들기 위해 인스턴스를 만들고 RMI 레지스트리에 등록
3. rmiregistry 실행
- 터미널을 띄워 rmiregistry를 실행 - 클래스에 접근할 수 있는 디렉터리에서 실행해야함
4.원격 서비스 실행
- 다른 터미널을 열고 서비스를 실행 -java MyRemoteImpl
클라이언트 코드 살펴보기
public class MyRemoteClient {
public static void main(String[] args){
new MyRemoteClient().go();
}
public void go() {
try {
MyRemote service = (MyRemote) Naming.lookup("rmi://127.0.0.1/RemoteHello");
String s = service.sayHello();
System.out.println(s);
}catch (Exception e){
e.printStackTrace();
}
}
}
- (MyRemote) Naming.lookup("rmi://127.0.0.1/RemoteHello");
- 레지스트리에서 리턴된 객체는 그냥 Object 형식이므로 반드시 캐스팅을 해야한다
- ip주소 또는 호스트 이름이 필요
- 서비스를 결합(재결합)할 때 지정했던 이름도 필요
- String s = service.sayHello();
- 보통 메소드를 호출할 때와 똑같은 방식으로 호출하면 됨 (RemoteException 발생 대비만 하면됨)
GumballMachine 클래스를 원격 서비스로 바꾸기
1. 원격 인터페이스 생성 - GumballMachineRemote Interface
import java.rmi.Remote;
public interface GumballMachineRemote extends Remote {
public int getCount() throws RuntimeException;
public String getLocation() throws RuntimeException;
public State getState() throws RuntimeException;
}
- 클라이언트에서 호출할 수 있는 메소드 정의
- 모든 리턴형식은 원시 형식 또는 Serializable
- 지원해야 하는 메소드 모두 RemoteException을 던질 수 있다
State Interface 수정
public interface State extends Serializable {
public void insertQuarter();
public void ejectQuarter();
public void turnCrank();
public void dispense();
}
- State의 서브클래스를 직렬화해서 네트워크를 전송할 수 있도록 Serializable 인터페이스 확장
State Interface를 구현한 서브 클래스 수정
public class NoQuarterState implements State{
private static final long serialVersionUID = 2L;
transient GumballMachine gumballMachine;
//기타
}
- State를 구현하는 모든 클래스에서 GuballMachine 인스턴스 변수를 선언하는 부분에 trasient 키워드 추가 => jvm 직렬화 제외
- 전부 직렬화 해서 보내는 일은 바람직하지 않음
2. 서비스 구현 클래스 수정 GumballMachine
public class GumballMachine extends UnicastRemoteObject implements GumballMachineRemote{
State state;
String location;
int count = 0;
public GumballMachine(String location, int count) throws RemoteException {
this.location = location;
}
public String getLocation() {
return location;
}
public State getState() {
return state;
}
public int getCount() {
return count;
}
public void setState(State state) {
this.state = state;
}
}
- GumballMachine 클래스를 UnicastRemoteObject의 서브 클래스로 만든다 => 원격 서비스 역할을 위해
- 슈퍼 클래스에서 RemoteException을 던질 수 있으므로, 생성자에도 RemoteException를 던질 수 있어야 한다
3. RMI 레지스트리 등록
public class GumballMachineTestDrive {
public static void main(String[] args){
GumballMachineRemote gumballMachine = null;
int count;
if(args.length <2){
System.out.println("gunballmachine <name> <inventory>");
System.exit(1);
}
try{
count = Integer.parseInt(args[1]);
gumballMachine = (GumballMachineRemote) new GumballMachine(args[0], count);
Naming.rebind("//"+args[0] +"/gumballmachine", gumballMachine);
}catch (Exception e){
e.printStackTrace();
}
}
}
- Naming.rebind()메소드를 호출한다. GumballMachine 스텁을 gumballmachine이라는 이름으로 등록한다
GumballMonitor 클라이언트 코드 살펴보기
public class GumballMonitor {
GumballMachineRemote machine;
public GumballMonitor(GumballMachineRemote machine) {
this.machine = machine;
}
public void report(){
try{
System.out.println("뽑기 기계 위치 : "+machine.getLocation());
System.out.println("현재 재고 : "+ machine.getCount());
System.out.println("현재 상태 : "+ machine.getState());
}catch(Exception e){
e.printStackTrace();
}
}
}
- GumballMachine 구상 클래스 대신 원격 인터페이스를 사용한다
- 메소드를 네트워크로 호출하므로 RemoteException이 던져질 때 잡아낼 수 있어야한다.
정리
원격 프록시는 다른 JVM에 있는 객체의 대리인에 해당하는 로컬객체이다. 프록시의 메소드를 호출하면 그 호출이 네트워크로 전달되어 결국 원격 객체의 메소드가 호출된다. 그리고 그 결과는 다시 프록시를 거쳐서 클라이언트에게 전달 된다.
참고
헤드퍼스트 디자인 패턴
https://watevathing.tistory.com/12
https://lee1535.tistory.com/101
https://gre-eny.tistory.com/253
'JAVA > 디자인 패턴' 카테고리의 다른 글
[Proxy Pattern] 프록시 패턴 - 보호 프록시 (0) | 2023.07.04 |
---|---|
[Proxy Pattern] 프록시 패턴 - 가상 프록시 (0) | 2023.07.03 |
[Proxy Pattern] 프록시 패턴이란? (0) | 2023.07.03 |
[Adapter Pattern] 어댑터 패턴이란? (0) | 2023.06.28 |
[Design Pattern] 디자인 패턴이란? (0) | 2023.06.28 |