finerss's world!

메서드 호출은 항상 같은 힙에 들어있는 두 객체 사이에서 이루어진다.

 class Foo{
       void go()  {
             Bar b = new Bar();
             b.doStuff();
       }

        public static void main (String[] args) {
                 Foo f = new Foo();
                 f.go();
       }
}


위 코드의 두 객체는 JVM에서 관리하는 같은 힙안에 들어있으며 힙에 있는 객체에 접근하는 방법을 나타내는
레퍼런스 변수를 비트에 채워 넣는 일은 JVM에서 맡아 처리한다. JVM은 항상 각 객체가 어디에 있는지,
어떻게 그 객체에 접근할 수 있는지를 알고 있다. 하지만 JVM에서는 그 자신의 힙에 있는 인스턴스에 대한 것만 알고 있다.
예를들어, 한 시스템에서 돌아가고 있는 JVM에서 다른 시스템에서 돌아가고 있는 JVM의 힙 공간에 대해서
알수 있게 할수는 없다.
사실 어떤 시스템에서 돌아가고 있는 JVM에서 같은 시스템에서 돌아가고 있는 JVM에 대해서도 전혀
알 수가 없다. JVM이 물리적으로 같은 시스템에 있는 것인지 다른 시스템에있는것인지는 중요하지않다.
중요한것은 JVM 두개가 별도로 호출된 JVM 이라는 것이다.


다른 시스템에서 돌아가고 이쓴ㄴ 객체에 있는 메소드를 호출하려면 어떻게해야하나?

방법은 RMI(원격 메소드 호출, Remote Method Invocation)

원격 메서드 호출 방법 설계
서버, 클라이언트,서버 보조 객체, 클라이언트 보조 객체, 이렇게 네가지를 만든다.



'보조객체(helper)'의 역활

'보조객체(helper)'는 실제 통신 작업을 처리하는 객체다. "클라이언트가 로컬 객체에 있는 메소드를 호출하는 척" 하는 것을
가능하게 해준다. 사실, 클라이언트는 로컬 객체에 있는 메소드를 호출한다. 클라이언트는 그 보조 객체를 실제 서비스로
간주하고 클라이언트 보조 객체에 대한 메소드를 호출한다. 결국, 클라이언트 보조 객체는 진짜 객체를 대신하는 역활을 한다.

즉, 클라이언트 보조 객체는 "클라이언트에서 호출하려고 하는 메소드를 가지고 있는 객체인 척" 하는 객체다.

하지만 클라이언트 보조 객체는 사실 진짜 원격 서비스가 아니다.
(서비스에서 제공하는 것과 같은 메소드가 있 기 때문에) 원격 서비스인 것처럼 행동하긴 하지만 거기에는 클라이언트에서
예상하고 있는 실제 메소드 처리 코드 같은 것은 없다. 대신 클라이언트 보조 객체에서는
서버와 접촉하고, 메소드 호출에 대한정보(메소드명, 인자 등)를 전송하고 서버에서 결과를 리턴할때까지 기다린다.

서버 쪽에서는 서비스 보조 객체에서, (소켓 연결을 통해) 클라이언트 보조 객체에서 보낸 요청을 받아서 호출에 대한
정보를 열어보고 진짜 서비스 객체에 있는 진짜 메소드를 호출한다.

서비스 보조 객체는 서비스로부터 리턴값을 받아 포장한후 다시 (소쳇의 출력 스트림을 통해) 클라이언트 보조 객체로 보낸다.
클라이언트 보조 객체는 그 포장을 풀고 그 정보를 다시 클라이언트 객체로 리턴한다.

메서드 호출과정


1. 클라이언트 객체에서 클라이언트 보조 객체에 있는 메서드를 호출한다.
2. 클라이언트 보조 객체에서 메서드 호출에 대한 정보(인자, 메소드명 등)를 포장해서
    네트워크를 통해 서비스 보조 객체로 보낸다.
3. 서비스 보조 객체에서 클라이언트 보조 객체로부터 받은 정보를 풀어서 어떤 객체의 어떤 메소드를 호출할지 알아낸 다음
    진짜 서비스 객체에있는 진짜 메소드를 호출한다.


자바 RMI가 클라이언트와 서비스 보조 객체를 제공한다.

자바에서는 RMI가 클라이언트와 서비스 보조 객체를 만들어주고 클라이언트 보조 객체가 진짜 서비스인 것처럼 보이게하는
방법도 알고 있다. 즉, RMI에서 클라이언트 보조 객체에 원격 서비스에 대해 호출하고자 하는 것과 같은 메소드를 배정해 주는 방법도
알고있다.

그리고 RMI는 클라이언트에서 클라이언트 보조 객체(진짜 서비스인 것처럼 행동하는 객체)를 찾고 가져올 수 있게 해주는 룩업 시스템을 비롯한 모든 필요한 기반을 제공한다.

RMI를 사용할때 JRMP, IIOP 이렇게 두가지 규약가운데 하나를 사용할수있는데 JRMP는 RMI에서 원래 사용하던 규약으로 자바에서->자바로 원격 호출을 하기 위한 용도로 만들어진 반면, IIOP는 CORBA용 규약이며 자바가 아닌 원격 객체에 있는 것도 호출할수 있는 규약이다.
양쪽에서 모두 자바를 사용하지않으면 엄청나게 많은 해석과 변환이 이루어져야하기 떄문에 CORBA는 RMI에비해 쓰기가 어렵다.


RMI에서 클라이언트 보조 객체는 '스터브(stub)'고
서버 보조 객체는 '스켈레톤(skeleton)' 이다.



코드로 보면 이해하기 쉽다.

서버용코드


-원격 인터페이스(service)
 import java.rmi.*;
//RemoteException과 Remote인터페이스가 java.rmi 패키지에 들어있다.
public interface MyRemote extends Remote {
 //인터페이스에서 반드시 java.rmi.Remote를 확장시켜야한다.
           public String sayHello() throws RemoteException;
           //모든 원격 메소드에 RemoteException을 선언해야한다.
}

-원격 서비스(serviceimpl, 인터페이스를 구현한 클래스)
import jave.rmi.*;
import java.rmi.server.*;
//UnicastRemoteObject가 java.rmi.server패키지에 들어있다.
public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote {
//앞서 만든 원격 인터페이스를 구현해야 한다.
//원격 객체를 만들 때는 UnicastRemoteObject를 확장하는 것이 가장 쉬운 방법이다.
             public String sayHello() {
                       return "Server says, 'Hey'";
             }
             // 당연히 모든 인터페이스 메소드를 구현해야 한다. 하지만 RemoteException을 선언하지 않아도 된다.

            public MyRemoteImpl() throws RemoteExeption {}
           //상위 클래스 생성자(UnicastRemoteObject생성자)에서 예외를 선언하기 떄문에 반드시 생성자를 만들어야한다.
              이렇게 해야 생성자에서 위험한코드(상위클래스 생성자)를 호출한다는 것을 선언할수 있기떄문이다.

            public static void main (String[] args) {

                      try{
                          MyRemote service = new MyRemoteImpl();
                          Naming.rebind("Remote Hello", service);
                          //원격 객체를 만들고 Naming.rebind()라는 정적메소드를 써서 RMI 레지스트리에 결합시킨다.
                             나중에 클라이언트에서 RMI레지스트리에 있는 서비스를 찾을떄 바로 여기에서 지정한이름을 사용한다.

                     }catch ( Exception ex){
                          ex.printStackTrace();
                     }
            }
}


클라이언트코드

import java.rmi.*;
//Naming 클래스(RMI 레지스트리 룩업에 필요함) 가 java.rmi 패키지에 들어있다.

public class MyRemoteClient{
          public static void main (String[] args) {
                    new MyRemoteClient().go();
          }

          public void go(){

                    try{
                         MyRemote service = 1.(MyRemote) Naming.lookup("2.rmi://127.0.0.1/3.Remote Hello");
                         //1. 레지스트리에서 보낸 객체는 Object 유형이므로 반드시 캐스트를 해야함.
                         //2. ip주소 또는 호스트명이필요하다.
                         //3. 서비스를 연결/재연결 할때 사용한 이름도 필요하다.
                         String s = service.sayHello();
                        //  일반 메소드 호출방법하고 똑같다. (RemoteException을 처리하거나 선언해야한다는점을 빼면)
                         System.out.println(s);
                     }catch(Exception ex){
                         ex.printStackTrace();
                     }
           }
}



그림으로 보면


1.클라이언트에서 RMI 레지스트리에 대한 룩업 작업을 한다.
   Namin.lookup("rmi://127.0.0.1/Remote Hello");

2. RMI 레지스트리에서 스터브 객체를 리턴한다.
   (이 객체는 lookup()메소드의 리턴값 형태로 전달된다)
   그리고 RMI에서 스터브를 자동으로 역직렬화 해준다. 클라이언트에 스터브 클래스(rmic가 만들어준것)가 없으면 스터브가 역직렬화되지않음

3. 클라이언트에서 실제 서비스에 있는 메소드를 호출하는 것과 같은 방법으로 스터브에 있는 메소드를 호출한다.

'공부 > Spring' 카테고리의 다른 글

Spring IOC  (0) 2011.12.12
About Spring  (0) 2011.12.12
엔티티 빈  (0) 2011.07.26