책너두 (헤드 퍼스트 디자인 패턴) 36일차 (~488p)

요약

  • 프록시 패턴
    • 서버에 필요한 코드 살펴보기
      • 작동 방식
    • 클라이언트 코드 살펴보기
    • GumballMachine 클래스를 원격 서비스로 바꾸기
    • RMI 레지스트리 등록하기
    • GumballMonitor 클라이언트 코드 고치기

메모

서버에 필요한 코드 살펴보기

[원격 인터페이스]

import java.rmi.*;

public interface MyRemote extends Remote {
    public String sayHello() throws RemoteExceptionl;
}

[원격 서비스를 구현한 클래스]

import java.rmi.*;
import java.rmi.server.*;

public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote {
    private static final long serialVersionUID = 1L;

    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 ex) {
            ex.printStackTrace();
        }
    }
}
  • 원격 객체를 만드는 가장 쉬운 방법은 UnicastRemoteObject 확장하기 임.
  • 원격 인터페이스를 반드시 구현해야 함.
  • 원격 객체의 인스턴스를 만들고, Naming.rebind() 정적 메소드를 써서 rmiregistry에 결합함.
    • 클라이언트에서 RMI 레지스트리로 서비스를 검색할 때는 여기에서 지정한 이름을 사용함.
  • 클라이언트는 스텁 객체(프록시)를 가져와야 함.
    • 이때 RMI 레지스트리가 활약함.
    • 클라이언트는 룩업(lookup)으로 스텁 객체를 요청함.
    • 이름을 건네주면, 그 이름에 맞는 스텁 객체를 요구함.
MyRemote service = (MyRemote) Naming.lookup("rmi://127.0.0.1/RemoteHello");
  • 클라이언트는 항상 서비스를 원격 인터페이스 형식으로 지정함.
    • 원격 서비스를 구현한 클래스의 이름은 전혀 몰라도 됨.
  • lookup()은 Naming 클래스에 들어있는 정적 메소드임.
    • 서비스를 등록할 때 사용한 이름을 적어줘야 함.
    • 서비스가 돌아가고 있는 시스젬의 호스트 이름 or IP주소를 함께 적어야 함.
    • 리턴된 스텁은 인터페이스 형식으로 캐스팅해야 함. → lookup() 메소드는 항상 Object 형식의 객체를 리턴하기 때문.

작동 방식

1. 클라이언트에서 RMI 레지스트리를 룩업함.

Naming.lookup("rmi://127.0.0.1/RemoteHello");

2. RMI 레지스트리에서 스텁 객체를 리턴함.

  • 스텁 객체는 lookup() 메소드의 리턴 값으로 전달됨.
  • RMI에서는 그 스텁을 자동으로 역직렬화함.
  • 이때, rmic에서 생성해준 스텁 클래스는 반드시 클라이언트에만 있어야 함.
    • 이 클래스가 없으면 역직렬화 할 수 없음.

3. 클라이언트는 스텁의 메소드를 호출함.

  • 스텁이 진짜 서비스 객체라고 생각함.

클라이언트 코드 살펴보기

import java.rmi.*;

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 ex) {
            ex.printStackeTrace();
        }
    }
}
  • RMI 레지스트리 룩업을 처리하는 Naming 클래스가 java.rmi 패키지에 들어있음.
  • service.sayHello()와 같이 그냥 보통 메소드를 호출할 때와 똑같은 식으로 호출하면 됨.
    • RemoteException이 발생할 것에 대비하기만 하면 됨.

GumballMachine 클래스를 원격 서비스로 바꾸기

1. GumballMachine의 원격 인터페이스를 만듦.

import java.rmi.*;

public interface GumballMachineRemote extends Remote {
    public int getCount() throws RemoteException;
    public String getLocation() throws RemoteException;
    public State getState() throws RemoteException;
}
  • 모든 리턴 형식은 원시 형식 또는 Serializable 이어야 함.
  • 지원해야 하는 메소드 모두 RemoteException을 던질 수 있음.

2. 인터페이스의 모든 리턴 형식을 직렬화할 수 있는지 확인해야 함.

  • State 클래스는 직렬화할 수 없는 리턴 형식이므로 고쳐야 함.
import java.io.*;

public interface State extends Serializable {
    public void inserQuater();
    ...
}
  • 여기서, 모든 State 객체에는 뽑기 기계의 메소드를 호출하거나 상태를 변경할 때 사용하는 뽑기 기계 레퍼런스가 들어 있음.
    • State 객체가 전송될 때, GumballMachine 클래스도 전부 직렬화해서 같이 보내는 일은 바람직하지 않음.
    • 다음과 같이 간단하게 수정하자.
public class NoQuaterState implements State {
    private static final long serialVersionUID = 2L;
    transient GumballMachine gumballMachine;
    // 기타 메소드
}
  • transient 키워드를 쓰면, JVM에서 그 필드를 직렬화하지 않음.
    • 하지만, 객체를 직렬화해서 전송받은 후에 이 필드를 호출하면 안 좋은 일이 발생할 수 있다는 사실을 기억해야 함.
  • 이제 GumballMachine 클래스를 네트워크로 들어온 요청응ㄹ 처리하는 서비스로 고쳐야 함.
import java.rmi.*;
import java.rmi.server.*;

public class GumballMachine extends UnicaseRemoteObject implements GumballMachineRemote {
    private static final long serialVersionUID = 2L;
    // 기타 인스턴스 변수

    public GumballMachine(String location, int numberGumballs) throws RemoteException {
    // 생성자 코드
    }

    public int getCount() {
        return count;
    }

    public State getState() {
        return state;
    }

    public String getLocation() {
        return location;
    }
    // 기타 메소드
}
  • GumballMachine 클래스를 UnicastRemoteObject 서브클래스로 만들어야 원격 서비스 역할을 할 수 있음.

RMI 레지스트리 등록하기

  • 서비스 요청을 받아서 처리하도록 시동을 거는 일만 남음.
    • p482 참고

GumballMonitor 클라이언트 코드 고치기

  • 네트워크로 데이터를 받아오기로 계획을 세움.
  • p483 참고
📍 프록시에 있는 메소드를 호출하면 네트워크로 메소드 호출이 전달됨. 호출 결과로 String, 정수, State 객체가 리턴됨. 프록시를 사용하므로 GumballMonitor는 원격 호출을 하고 있다는 사실을 몰라도 됨. 물론 RemoteException을 대비해야 함.

댓글

Designed by JB FACTORY