企業中的 RMI-IIOP

RMI 和 CORBA 常被視爲相互競爭的技術,因爲兩者都提供對遠程分佈式對象的透明訪問。但這兩種技術實際上是相互補充的,一者的長處正好可以彌補另一者的短處。RMI 和 CORBA 的結合產生了 RMI-IIOP,RMI-IIOP 是企業服務器端 Java 開發的基礎。在本文中,Java 開發者 Damian Hagge 簡要介紹了 RMI-IIOP,然後爲您展示如何構建和運行一個簡單的、基於 Java 的 RMI-IIOP 客戶機/服務器應用程序。請親自看看 RMI 能在 IIOP 上工作得多麼好。

1997 年,IBM 和 Sun Microsystems 啓動了一項旨在促進 Java 作爲企業開發技術的發展的合作計劃。兩家公司特別着力於如何將 Java 用作服務器端語言,生成可以結合進現有體系結構的企業級代碼。所需要的就是一種遠程傳輸技術,它兼有 Java 的 RMI(Remote Method Invocation,遠程方法調用)較少的資源佔用量和更成熟的 CORBA(Common Object Request Broker Architecture,公共對象請求代理體系結構)技術的健壯性。出於這一需要,RMI-IIOP 問世了,它幫助將 Java 語言推向了目前服務器端企業開發的主流語言的領先地位。

在本文中,我將簡要介紹 RMI-IIOP,目標是使您能開始在企業開發解決方案中使用這一技術。要解釋 RMI-IIOP 究竟是什麼,我認爲提供一些關於 CORBA 和 RMI 的信息是重要的,這些信息您在各個技術的典型介紹中可能找不到。如果您對 CORBA 或 RMI 的基礎知識不熟悉,我建議您在往下讀之前先閱讀一些介紹性信息。請參閱參考資料,那裏挑選了一些文章和教程。

在我具體討論 RMI-IIOP 之前,我們將先看一下 CORBA 和 RMI 用來對請求進行數據編入的機制。CORBA 將是我們的主要示例,因爲 RMI-IIOP 數據編入是建立在 CORBA 傳輸協議(IIOP)的基礎上的。我們將回顧一下該傳輸協議和 ORB(object request broker,對象請求代理)在網絡上發送請求、定位遠程對象和傳輸對象方面的基本功能。

遠程對象傳輸
對 CORBA 請求進行數據編入是通過使用 IIOP 協議做到的。簡言之,IIOP 將以標準化格式構造的任何 IDL(Interface Definition Language,接口定義語言)的元素表示爲一系列字節。那就假設有一個 Java 客戶機正在將一個 CORBA 請求分派到 C++ 服務器吧。客戶機應用程序以 Java 接口的形式擁有遠程對象的引用,並調用該接口的一個操作。本質上是,接口調用它對該操作的相應實現,這個實現將位於存根(stub)(存根是您將已經用 idlj 從 IDL 生成了的)。

存根把方法調用分派到 ORB 中,ORB 由兩部分組成:客戶機 ORB 和服務器 ORB。客戶機 ORB 的職責是對請求進行數據編入,放到網絡上,傳往特定位置。服務器 ORB 的職責是偵聽從網絡上傳下來的請求,並將這些請求轉換成語言實現能夠理解的方法調用。要了解對 CORBA ORB 的角色的更深入討論,請參閱參考資料部分。

存根分派了方法調用之後,客戶機 ORB 將請求和所有參數轉換成標準化字節格式,在這種情況中是 IIOP。接着,請求通過導線被髮送到服務器 ORB,服務器 ORB 應該正在偵聽傳入請求。服務器端 ORB 將讀進數據的字節並將請求轉換成對 C++ 服務器實現有意義的東西。C++ 服務器方法將執行它的功能(即調用所請求的方法)並使用相同的機制通過 IIOP 將結果返回給客戶機。

RMI 以類似的方式處理請求,但是它使用 JRMP(Java Remote Messaging Protocol,Java 遠程消息傳遞協議)作爲其傳輸協議。當然,RMI 傳輸還涉及 Java 對象的序列化。

CORBA 和 RMI 的差異
  • CORBA 運行在 IIOP 協議之上;RMI 使用 JRMP。
  • CORBA 是獨立於語言的;RMI 是純粹 Java 到 Java 的。
  • RMI 使用 JNDI 定位遠程對象;CORBA 使用 CosNaming。
  • RMI 會將對象序列化;CORBA 則不然。

遠程對象定位
CORBA 使用 CosNaming 命名服務定位遠程對象。CosNaming 爲名稱服務器保存對 CORBA 服務器進程的綁定(或引用)提供了一個框架。當 CORBA 客戶機向名稱服務發送 CosNaming 請求,請求給定名稱的服務器進程時,名稱服務返回該進程的可互操作對象引用(interoperable object reference(IOR))。接着,客戶機使用該 IOR 直接與服務器進程通信。

IOR 包含關於服務器進程的信息,例如服務器進程的位置。CosNaming 服務的缺點之一是,IOR 對人類而言是難以看懂的 — 至少對我們這些沒有電子大腦的人來說是這樣。相反地,RMI 對用戶則要友好一些。它使用運行在 JNDI 之上的註冊中心(與命名服務極爲相似)來定位遠程對象。RMI 註冊中心使用 Java Reference 對象(它由若干個 RefAddr 對象組成)來識別和定位遠程對象。這些 Java 對象比 IOR 對用戶更加友好。

不久前,COBRA 將可互操作命名服務(Interoperable Naming Service(INS))結合進了它的對象-定位(object-location)模式。INS 在 CosNaming 上運行,使用人類可以閱讀的 URL 作它的對象位置。INS 不使用命名服務;相反地,它將調用直接發送到指定的 URL。請參閱參考資料瞭解關於 INS 的更多信息。

RMI 對 CORBA
那麼,哪一個更好呢:是 CORBA 還是 RMI?答案取決於您想做什麼。CORBA 是一個運行在業界標準的第三或第四代協議上的、經過試驗和測試的大體系結構。如果考慮到 CORBA 提供的所有附件(例如:事務處理、安全攔截器、事件通道,還有更多)的話,則 CORBA 看來是企業應用程序的解決方案。CORBA 的最大缺點是它很複雜。要熟練使用 CORBA,開發者通常要經歷陡峭的培訓曲線。

相反地,RMI 相當容易學習。創建一個客戶機/服務器實現,綁定到註冊中心和遠程對象,使用 RMI 調用和/或接收請求都相當簡單。RMI 的資源佔用量也比 CORBA 小得多,因爲 JRMP 是開銷比 IIOP 小得多的協議。但是,RMI 缺乏 CORBA 的工業級的附件,而且是純基於 Java 的機制。那麼,我們真正需要的就是 RMI 的靈活性和易用性以及 CORBA 的企業就緒性,對嗎?那就開始討論 RMI-IIOP 吧。

爲什麼是 RMI-IIOP?
  • RMI-IIOP 兼有 CORBA 的強度和 RMI 的靈活性。
  • 開發者很容易就可以使用 RMI-IIOP,RMI-IIOP 也易於集成到多數企業基礎架構中。

RMI-IIOP 概覽
RMI-IIOP 讓您僅需極少修改就可以在 IIOP 上運行 RMI 調用。藉助於 RMI-IIOP,您可以編寫簡單易懂的 Java 代碼,同時使用 CORBA 提供的豐富的企業功能套件。而且,代碼的靈活性足夠大,可以運行在 RMI IIOP 上。這意味着,您的代碼可以在純 Java 環境中運行(當小的資源佔用量和靈活性很關鍵時),或者對代碼作少量修改後集成到現有的 CORBA 基礎架構中。

RMI-IIOP 很強大的功能之一是,它讓您編寫純 Java 客戶機/服務器實現而不喪失 RMI 類序列化的靈活性。RMI-IIOP 通過覆蓋 Java 序列化並在導線上將 Java 類轉換成 IIOP 做到這一點。在另一端,Java 類被作爲 IIOP 從導線上讀下來,接着創建這個類的一個新實例(使用反射),類的所有成員的值都完整無缺 — :這就是 IIOP 上的 Java 序列化!

爲了讓 RMI-IIOP 實現透明的對象定位,ORB 供應商歷史上曾經使用 Java CosNaming 服務提供者(或用外行人的話說,是插件)。該插件在 JNDI API 之下工作,訪問 CORBA 命名服務。儘管我沒有在這裏花篇幅來說明原因,但這種命名解決方案並不理想。其結果是,許多供應商 — 尤其是應用服務器供應商 — 爲 RMI-IIOP 開發了專門的對象定位機制。

RMI-IIOP 也支持作爲 Java CosNaming 服務的一個擴展的 INS。因爲我相信 INS 將確定對象定位的未來方向,所以我們在本文將討論的代碼示例使用 INS。

注:因爲 Sun 尚未完全遵循 OMG INS 標準,也尚未公開 org.omg.CORBA.ORB 接口的 register_initial_reference,所以本文提供的源代碼將不能與 Sun JDK 一起工作。您將需要 IBM Developer Kit for Java technology,版本 1.3.1 或更高版本。不過,我已經創建了一個使用命名服務的與 Sun 兼容的示例,您可以從參考資料部分下載它。

自己動手構建 RMI-IIOP
說得夠多了,讓我們來編寫代碼吧!在以下幾部分中,我們將構建一個簡單的、基於 Java 的客戶機/服務器 RMI-IIOP 應用程序。這個應用程序由三個部分組成:RMI 接口、服務器應用程序和客戶機應用程序。示例以在 IIOP 之上的 Java 序列化爲特色,所以您可以看到 Java 類如何被客戶機實例化,如何傳遞到服務器,由服務器更改,然後將所有修改完整地回傳到客戶機。

第 1 部分:定義接口
在 RMI-IIOP 下,我們可以選擇使用 RMI 或 IDL 來定義接口。因爲我們想看看 RMI 如何運行在 IIOP 上,所以我們將使用 RMI 定義示例接口。清單 1 是我們的簡單示例的 RMI 接口:

清單 1. RMIInterface.java

/*
 * Remote interface
 */
public interface RMIInterface extends java.rmi.Remote {
    public String hello() throws java.rmi.RemoteException;
    public SerClass alterClass(SerClass classObject) 
       throws java.rmi.RemoteException;
}



RMIInterface 定義一個 hello() 方法和一個 alterClass(SerClass) 方法。後一個方法用 SerClass 作參數,SerClass 是一個實現 Serializable 的 Java 類,alterClass(SerClass) 方法返回一個類型與其參數的類型相同的類。SerClass 是一個有幾個成員的簡單的類,每個成員有相應的 getter 方法。這些方法如清單 2 所示:

清單 2. SerClass.java
/**
 *  This class is intended to be serialized over RMI-IIOP.
 */
public class SerClass implements java.io.Serializable {
    // members
    private int x;
    private String myString;

    // constructor
    public SerClass(int x, String myString) 
       throws java.rmi.RemoteException {
        this.x=x;
        this.myString=myString;
    } 
    
    // some accessor methods
    public int getX() {  return x;}
    public void setX(int x) { this.x=x; }
    public String getString() {  return myString;  }
    public void setString(String str) { myString=str; }
}

這就是我們簡單的接口的全部。現在我們來研究一下服務器類。

第 2 部分:構建服務器
我們將使用一個既充當 RMIInterface 實現類又包含 main 方法(以啓動我們的服務)的服務器類(Server.java)。Server.java 繼承 javax.rmi.PortableRemoteObject。這樣,它就包含了將自己作爲 Remote 接口綁定到 ORB 和開始偵聽請求所需要的全部功能。清單 3 是該服務器的代碼:

清單 3. Server.java
/*
 * Simple server
 */
import java.util.*;
import java.rmi.Remote;
import java.rmi.RemoteException;
import javax.rmi.PortableRemoteObject;
import javax.rmi.CORBA.Tie;
import javax.rmi.CORBA.Util;
import org.omg.PortableServer.POA;
import org.omg.PortableServer.*;
import org.omg.PortableServer.Servant;
import org.omg.CORBA.ORB;

public class Server extends PortableRemoteObject 
    implements RMIInterface {
    // must explicitly create default constructor 
    // to throw RemoteException
    public Server() throws RemoteException {
    }

    // implementation of RMIInterface methods
    public String hello() throws RemoteException {
        return "Hello there!";
    }

    public SerClass alterClass(SerClass classObject) 
        throws RemoteException {
        // change the values of SerClass and return it.
    // add 5 to X
        classObject.setX( 
           classObject.getX() + 5 ); 
    // alter the string
        classObject.setString( 
           classObject.getString() + " : I've altered you" ); 
        return classObject;
    }   

    public static void main(String[] args) {
        try {
            // create the ORB passing in the port to listen on
            Properties props = new Properties();
            props.put("com.ibm.CORBA.ListenerPort","8080");
            ORB orb = ORB.init(args, props);
    
            // instantiate the Server
            // this will automatically call exportObject(this)
            Server s = new Server();
            
            // now get the Stub for our server object - 
         // this will be both
            // a remote interface and an org.omg.CORBA.Object
            Remote r=PortableRemoteObject.toStub(s);                
    
            // register the process under the name 
         // by which it can be found    
            ((com.ibm.CORBA.iiop.ORB)orb).
            register_initial_reference("OurLittleClient",
            (org.omg.CORBA.Object)r);
    
            System.out.println("Hello Server waiting...");
            // it's that easy - 
        // we're registered and listening for incoming requests
            orb.run();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

呃,這裏發生着什麼呢?
服務器應用程序的代碼很長,那我們就分開來講吧。首先,如前面提到過的,Server 類實現 RMIInterface 併爲它的所有方法提供實現。您可以在代碼的前面部分看到 RMIInterfacehello() 方法和 alterClass(SerClass) 方法的實現。hello() 方法只是返回字符串“Hello there!”。alterClass(SerClass) 方法用 SerClass 對象作參數,修改成員的值,然後返回新的對象 — 全都通過 RMI-IIOP。

Server.java 的 main 方法初始化一個 ORB。這個 ORB 將設置爲 8080 的 com.ibm.CORBA.ListenerPort 屬性作爲參數傳入。這將使得 ORB 在端口 8080 上偵聽傳入請求。請注意,com.ibm.CORBA.ListenerPort 是一個專有的 IBM 屬性。如果您想在另一供應商的 ORB 上運行這些代碼,那您應該參閱該供應商的文檔,找到適當的屬性。(Sun 使用 com.sun.CORBA.POA.ORBPersistentServerPort,但它只在您使用 POA(portable object adapter,可移植對象適配器)伺服器(servant)時才能夠工作。)

初始化 ORB 後,main 方法接着對 Server 對象進行實例化。因爲這個 server 對象也是一個 PortableRemoteObject,所以缺省構造函數會自動調用 exportObject(this)。這個對象現在已經就緒於接收遠程調用。

接着,我們需要通過調用 ORB.register_initial_reference(String,orb.omg.CORBA.Object) 註冊這個對象。爲此,我們需要把我們的 server 對象作爲 org.omg.CORBA.Object 的引用。調用 PortableRemoteObject.toStub(s) 實現了這一點,因爲所返回的對象都實現了 java.rmi.Remoteorg.omg.CORBA.Object

然後,返回的 org.omg.CORBA.Object 對象向服務器端 ORB 註冊爲“OurLittleClient”。爲了確保 INS 請求能夠定位對象,我們使用註冊調用 register_initial_reference。當 INS 調用進入 ORB 時,ORB 將查找已經以正在被請求的名稱註冊的對象。由於我們將對象註冊爲“OurLittleClient”,所以,當一個 INS 調用進入我們的服務器 ORB 要求“OurLittleClient”時,我們將知道客戶機正在查找的是哪個對象。

最後,我確信您已經注意到我們將 ORB 強制轉型成 com.ibm.CORBA.iiop.ORB。因爲 Sun 尚未公開 org.omg.CORBA.ORB 接口的 register_initial_reference,所以 IBM SDK 也不能將它公開。因此,我們必須將我們的 ORB 強制轉型成 IBM ORB。隨着 Sun 越來越遵循 OMG,JDK 的未來版本(1.4.0 後)將可能不需要這種強制轉型。

就是這樣!很簡單吧 — 嗯,是有點。我們的服務器現在正在等待傳入客戶機 INS 請求。但客戶機怎麼樣呢?

第 3 部分:構建客戶機
客戶機應用程序的代碼如清單 4 所示:

清單 4. Client.java
/*
 * Client application
 */
import javax.rmi.PortableRemoteObject;
import org.omg.CORBA.ORB;

public class Client {
  public static void main(String[] args) {
    try {
      ORB orb = ORB.init(args, null);
    
         // here's the URL for the local host
         String INSUrl = 
        "corbaloc:iiop:1.2@localhost:8080/OurLittleClient";  
       
         // get the reference to the remote process
         org.omg.CORBA.Object objRef=orb.string_to_object(INSUrl);
         // narrow it into our RMIInterface
         RMIInterface ri = 
  (RMIInterface)PortableRemoteObject.narrow(objRef, RMIInterface.class);
        
      // call the hello method
         System.out.println("received from server: "+ri.hello()+"/n");  
    
      // try RMI serialization
         SerClass se = new SerClass(5, "Client string! ");
         // pass the class to be altered on the server
         // of course behind the scenes this class is being 
      // serialized over IIOP
         se = ri.alterClass(se);
         // now let's see the result
         System.out.println("Serialization results :/n"+
            "Integer was 5 now is "+se.getX()+"/n"+
            "String was /"Client String! /" 
         now is /""+se.getString()+"/"");   
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

如何分解客戶機代碼
客戶機代碼比服務器代碼要簡單一些。我們初始化一個 ORB,然後調用 string_to_object(String),其中的 string 是我們的 INS URL。構造 INS URL 相當簡單:首先,我們指定我們使用 corbaloc URL(請參閱參考資料)和 IIOP 協議版本 1.2。接着,我們將主機名(www.whatever.com)和要連接的端口添加進去。最後,我們指定我們要查找的服務的名稱。結果 INS URL 是 corbaloc:iiop:1.2@localhost:8080/OurLittleClient

當我們將這個 URL 傳遞到 ORB.string_to_object(String) 時,ORB 將分派一個請求到所指定的服務器,以請求所請求的服務。假設一切運轉正常,則 ORB 將接收回該服務的一個對象引用(實際上是一個 IOR)。然後,我們將該對象引用強制轉型(narrow)成我們能夠使用的東西,即 RMIInterface,這樣,我們就爲開始調用方法做好了準備。

在調用了簡單的 hello 方法(它應該不需要任何解釋吧)之後,我們可以開始探討 RMI-IIOP 的序列化功能了。首先,我們創建一個 SerClass,一個可序列化的 Java 類,並初始化它的成員變量。接着,我們將這個類傳入到我們的方法,方法通過 IIOP 將類寫出到服務器。服務器讀入類並將它重創建爲服務器端 Java 對象,修改它的成員值,然後返回它(使用 IIOP)作爲方法的返回值。當接收到在遠程方法調用之後重創建的對象時,我們看到它的成員確實已被服務器修改了。就是這麼簡單:在 IIOP 上進行 Java 序列化。

第 4 部分:運行示例
請注意,我們這裏所創建的示例必須在 IBM Developer Kit for Java technology,版本 1.3.1 或更高版本中運行。如果您寧願使用 Sun JDK,請下載特定於 Sun 的源代碼,您應該在 Sun 1.4.0 JDK 或更高版本中運行它。這個源代碼包括一個解釋 IBM SDK 版本和 Sun JDK 版本之間的差異的 readme.txt 文件。如果您沒有 IBM Developer Kit for Java technology(而您又想要一個),請現在就下載一個;它們是免費的。

這裏是運行示例的步驟:

  1. 下載源文件

  2. 輸入 javac *.java,javac 所有文件。

  3. 對 server 類運行 rmic(帶 IIOP 標誌):rmic -iiop Server

  4. 啓動服務器:在 Windows 中,請輸入 start java Server

  5. 啓動客戶機:在 Windows 中,請輸入 start java Client


關於 RMI-IIOP 和 EJB 組件的一點註釋
EJB 2.0 規範指出,EJB 組件必須能在 RMI 和 RMI-IIOP 上運行。添加 RMI-IIOP 作爲針對 EJB 組件的在線協議,已經給將 J2EE 環境集成到現有的企業基礎設施(多數是 CORBA 相當密集的)帶來了很大幫助。但它也引起了一些問題。

簡單地說,就是將定製構建的組件和 EJB 組件集成起來要求您(開發者)處理管道(plumbing),否則在 EJB 體系結構中它們對您來說將很抽象。到目前爲止,還沒有解決這個問題的簡單方案,可能永遠也不會有。隨着諸如 Web 服務這樣的技術的發展,或許會出現解決方案,但目前尚未可知。

結束語:此後該做什麼
我希望本文已經向您展示了構建和運行 RMI-IIOP 客戶機/服務器應用程序是多麼容易。您可以修改一下我們使用的示例,用純 CORBA 替代客戶機或服務器,不過這樣做將除去您應用程序中的 Java 序列化。

如果您想在 CORBA 環境中使用 RMI-IIOP,那麼看看 IDL 如何映射成 Java 以及 Java 如何映射成 IDL 是值得的。如果您想在不安全的環境(即不是您自己的 PC)中部署 RMI-IIOP,那麼研究一下 CORBA 安全功能(如攔截器和 CORBA 安全模型)以及其它 CORBA 企業功能(如事務處理)是個不錯的主意。CORBA 所有的豐富功能在您運行 RMI-IIOP 時都可以使用。

 

參考資料


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章