一 .RMI概述

RMI(Remote Method Invocation)
    RMI是分布式對象軟件包,它簡化了在多臺計(jì)算機(jī)上的JAVA應(yīng)用之間的通信。必須在jdk1.1以上

RMI用到的類
     java.rmi.Remote                   所有可以被遠(yuǎn)程調(diào)用的對象都必須實(shí)現(xiàn)該接口
     java.rmi.server.UnicastRemoteObject  所有可以被遠(yuǎn)程調(diào)用的對象都必須擴(kuò)展該類

什么是RMI
    遠(yuǎn)程方法調(diào)用是一種計(jì)算機(jī)之間對象互相調(diào)用對方函數(shù),啟動對方進(jìn)程的一種機(jī)制,
使用這種機(jī)制,某一臺計(jì)算機(jī)上的對象在調(diào)用另外一臺計(jì)算機(jī)上的方法時(shí),使用的程
序語法規(guī)則和在本地機(jī)上對象間的方法調(diào)用的語法規(guī)則一樣。


優(yōu)點(diǎn)
這種機(jī)制給分布計(jì)算的系統(tǒng)設(shè)計(jì)、編程都帶來了極大的方便。
只要按照RMI規(guī)則設(shè)計(jì)程序,可以不必再過問在RMI之下的網(wǎng)絡(luò)細(xì)節(jié)了,如:TCP和Socket等等。
任意兩臺計(jì)算機(jī)之間的通訊完全由RMI負(fù)責(zé)。調(diào)用遠(yuǎn)程計(jì)算機(jī)上的對象就像本地對象一樣方便。

1、面向?qū)ο螅?br>RMI可將完整的對象作為參數(shù)和返回值進(jìn)行傳遞,而不僅僅是預(yù)定義的數(shù)據(jù)類型。
也就是說,可以將類似Java哈西表這樣的復(fù)雜類型作為一個(gè)參數(shù)進(jìn)行傳遞。

2、可移動屬性:
RMI可將屬性從客戶機(jī)移動到服務(wù)器,或者從服務(wù)器移動到客戶機(jī)。

3、設(shè)計(jì)方式:
對象傳遞功能使您可以在分布式計(jì)算中充分利用面向?qū)ο蠹夹g(shù)的強(qiáng)大功能,如二層和三層結(jié)構(gòu)系統(tǒng)。
如果用戶能夠傳遞屬性,那么就可以在自己的解決方案中使用面向?qū)ο蟮脑O(shè)計(jì)方式。
所有面向?qū)ο蟮脑O(shè)計(jì)方式無不依靠不同的屬性來發(fā)揮功能,如果不能傳遞完整的對象——包括實(shí)現(xiàn)和類型
——就會失去設(shè)計(jì)方式上所提供的優(yōu)點(diǎn)。

4、安全性:
RMI使用Java內(nèi)置的安全機(jī)制保證下載執(zhí)行程序時(shí)用戶系統(tǒng)的安全。
RMI使用專門為保護(hù)系統(tǒng)免遭惡意小程序侵害而設(shè)計(jì)的安全管理程序。
5、便于編寫和使用
RMI使得Java遠(yuǎn)程服務(wù)程序和訪問這些服務(wù)程序的Java客戶程序的編寫工作變得輕松、簡單。
遠(yuǎn)程接口實(shí)際上就是Java接口。
為了實(shí)現(xiàn)RMI的功能必須創(chuàng)建遠(yuǎn)程對象任何可以被遠(yuǎn)程調(diào)用的對象必須實(shí)現(xiàn)遠(yuǎn)程接口。但遠(yuǎn)程
接口本身并不包含任何方法。因而需要創(chuàng)建一個(gè)新的接口來擴(kuò)展遠(yuǎn)程接口。
新接口將包含所有可以遠(yuǎn)程調(diào)用的方法。遠(yuǎn)程對象必須實(shí)現(xiàn)這個(gè)新接口,由于新的接口擴(kuò)展了
遠(yuǎn)程接口,實(shí)現(xiàn)了新接口,就滿足了遠(yuǎn)程對象對實(shí)現(xiàn)遠(yuǎn)程接口的要求,所實(shí)現(xiàn)的每個(gè)對象都將
作為遠(yuǎn)程對象引用。

個(gè)人總結(jié):
    RMI說白了,就是提供了一種遠(yuǎn)程的方法調(diào)用。這種調(diào)用簡單方便,可以傳遞復(fù)雜java對象。現(xiàn)在流行的j2ee中的EJB的底層實(shí)現(xiàn)技術(shù)就是RMI,EJB的調(diào)用就是經(jīng)過封裝的,更高級的RMI調(diào)用。



下面我們就來寫一個(gè)RMI的程序:

一.創(chuàng)建RMI程序的6個(gè)步驟:
1、定義一個(gè)遠(yuǎn)程接口的接口,該接口中的每一個(gè)方法必須聲明它將產(chǎn)生一個(gè)RemoteException異常。
2、定義一個(gè)實(shí)現(xiàn)該接口的類。
3、使用RMIC程序生成遠(yuǎn)程實(shí)現(xiàn)所需的殘根和框架。
4、創(chuàng)建一個(gè)服務(wù)器,用于發(fā)布2中寫好的類。
5. 創(chuàng)建一個(gè)客戶程序進(jìn)行RMI調(diào)用。
6、啟動rmiRegistry并運(yùn)行自己的遠(yuǎn)程服務(wù)器和客戶程序。

二. 程序詳細(xì)說明

1.定義一個(gè)遠(yuǎn)程接口的接口,該接口中的每一個(gè)方法必須聲明它將產(chǎn)生一個(gè)RemoteException異常。

import java.rmi.Remote;
import java.rmi.RemoteException;
public interface I_Hello extends java.rmi.Remote   //需要從Remote繼承
{
       public String SayHello() throws RemoteException;   //需要拋出remote異常
}


   上面例子我們定義一個(gè)返回字符串的遠(yuǎn)程方法 SayHello(),這個(gè)遠(yuǎn)程接口 I_Hello必須是public的,它必須從java.rmi.Remote繼承而來,接口中的每一個(gè)方法都必須拋出遠(yuǎn)程異常java.rmi.RemoteException。


拋出這個(gè)異常的原因
由于任何遠(yuǎn)程方法調(diào)用實(shí)際上要進(jìn)行許多低級網(wǎng)絡(luò)操作,因此網(wǎng)絡(luò)錯(cuò)誤可能在調(diào)用過程中隨時(shí)發(fā)生。
因此,所有的RMI操作都應(yīng)放到try-catch塊中。
  
2、定義一個(gè)實(shí)現(xiàn)該接口的類。

import java.io.PrintStream;
import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;

public class Hello extends UnicastRemoteObject   //必須從UnicastRemoteObject  繼承
                   implements I_Hello
{
        public Hello() throws RemoteException     //需要一個(gè)拋出Remote異常的默認(rèn)初始化方法
        {
        }

        public String SayHello()     //這個(gè)是實(shí)現(xiàn)I_Hello接口的方法
        {
           return "Hello world !!";
        }
}

實(shí)現(xiàn)接口的類必須繼承UnicastRemoteObject類。
擴(kuò)展java.rmi.server.UnicastRemoteObject
UnicastRemoteObject顧名思義,是讓客戶機(jī)與服務(wù)器對象實(shí)例建立一對一的連接。

3、使用RMIC程序生成遠(yuǎn)程實(shí)現(xiàn)所需的殘根Stub 和 框架。
   2中的Hello 編譯好以后,我們就可以用RMIC命令來生成殘根Stub
   在Dos窗口里,到Hello.class 所在目錄,運(yùn)行以下命令:
   rmic Hello
   
   命令執(zhí)行完以后,將會在當(dāng)前目錄生成一個(gè) Hello_Stub.class 這個(gè)就是我們遠(yuǎn)程調(diào)用時(shí)需要的類

參考:
在RMI中,客戶機(jī)上生成的調(diào)動調(diào)用參數(shù)和反調(diào)動返回值的代碼稱為殘根。有的書上稱這部分代碼為“主干”。
服務(wù)器上生成的反調(diào)動調(diào)用參數(shù)和進(jìn)行實(shí)際方法調(diào)用調(diào)動返回值的代碼稱為框架。
生成殘根和框架的工具
Rmic命令行工具(RMI Compiler)
格式:
Rmic classname

4、創(chuàng)建一個(gè)服務(wù)器,用于發(fā)布2中寫好的類。

    import java.rmi.*;
public class RMI_Server
{
    public static void main(String[] args)
    {
        try
        {
            Hello hello = new Hello();                //實(shí)例化要發(fā)布的類
            Naming.rebind("RMI_Hello", hello);      //綁定RMI名稱 進(jìn)行發(fā)布
            System.out.println("=== Hello server Ready === ");
        }
        catch(Exception exception)
        {
            exception.printStackTrace();
        }
    }
}

5. 創(chuàng)建一個(gè)客戶程序進(jìn)行RMI調(diào)用。

import java.rmi.*;
public class RMI_Client {
    public static void main(String[] args) {
        try
        {
           I_Hello hello = (I_Hello) Naming.lookup("RMI_Hello");  //通過RMI名稱查找遠(yuǎn)程對象
            System.out.println(hello.SayHello());                        //調(diào)用遠(yuǎn)程對象的方法
        } catch (Exception e)
        {
          e.printStackTrace();
        }
    }


}


Naming.lookup("RMI_Hello") 其中的參數(shù)“RMI_Hello”只是針對本機(jī)的RMI查找,如果是異地的RMI調(diào)用請參照  rmi://127.0.0.1:1099/RMI_Hello       端口1099是默認(rèn)的RMI端口,如果你啟動 rmiregistry 的時(shí)候(見第6點(diǎn))沒有指定特殊的端口號,默認(rèn)就是1099

到此我們所有的代碼編寫都完成了,不過不要急著去運(yùn)行,請跟隨第6點(diǎn)去運(yùn)行,因?yàn)閞mi 調(diào)用還會遇到一些特別的情況,偶花了牛勁,才找到原因的,許多剛用RMI的人,常常被這些問題搞得吐血

6、啟動rmiRegistry并運(yùn)行自己的遠(yuǎn)程服務(wù)器和客戶程序。
1)服務(wù)器的運(yùn)行
    先在DOS下運(yùn)行 rmiregistry     這個(gè)命令是開啟RMI的注冊服務(wù),開啟以后我們的server程序才能調(diào)用rebing方法發(fā)布我們的類

    然后,運(yùn)行我們的server程序  RMI_Server    這里是最容易出錯(cuò)的,參見下面注意事項(xiàng)。
     注意:
         如果提示找不到Stub類,這個(gè)需要用下面的命令來運(yùn)行
java.exe -Djava.rmi.server.codebase=file:/E:\MIS_Interface\momo\TestEasy\classes/  RMI_Server

藍(lán)字部分指定了stub類的路徑。

  有人會問,我已經(jīng)把stub 通過-classpath 加到類路徑里面了,為什么還沒有提示這個(gè)錯(cuò)誤呢?原因是這樣的:這里提示的找不到stub類,不是由你寫的RMI_Server這個(gè)程序引起的,是由rmi注冊服務(wù)器報(bào)告的異常,也就是我們前面啟動的 rmiregistry ,因?yàn)槟銓懙腞MI_Server 要求RMI注冊服務(wù)器注冊一個(gè)新的類,自然RMI服務(wù)器必須知道你的類放在哪里,所以我們通過  -Djava.rmi.server.codebase 這個(gè)運(yùn)行參數(shù)來指定
  你也可以通過修改操作系統(tǒng)的classpath 環(huán)境變量來指定stub的位置,只不過太麻煩

2) 客戶端的運(yùn)行
      直接運(yùn)行RMI_Client  即可  注意 把 Stub 和 接口 I_Hello 加到類路徑里
  
    通常第一次運(yùn)行客戶端都會報(bào)一個(gè)錯(cuò)誤:   Access  XXXX 不記得具體的了,反正就是“訪問權(quán)限限制”,這是因?yàn)镽MI的服務(wù)需要授權(quán),外部程序才能訪問,所以我們要改動 jre的安全配置文件,來開放權(quán)限,  具體如下:

   打開你的jdk目錄下的這個(gè)文件 C:\Program Files\Java\jdk1.5.0_04\jre\lib\security\java.policy
在文件最后加入下面代碼:
grant {
           permission java.net.SocketPermission "*:1024-65535",
                "connect,accept";
           permission java.net.SocketPermission "*:80","connect";
        };
此代碼,開放了端口的connect訪問權(quán)限

注意 你應(yīng)該修改服務(wù)器那臺機(jī)子的安全配置文件,也就是你運(yùn)行 rmiregistry 和 RMI_Server的機(jī)子
另外,很多人修改完以后,仍然報(bào)這個(gè)錯(cuò)誤,多數(shù)情況是由于你沒有修改到正確的jdk 下的文件,而是修改到其他jdk的文件, 我們安裝oracle , Weblogic等等軟件的時(shí)候都會自帶一個(gè) jdk,他們會自動在操作系統(tǒng)的環(huán)境變量里面加入jdk的路徑,所以,你先要確定你運(yùn)行服務(wù)器端程序是用哪個(gè)jdk,再修改這個(gè)jdk下的配置文件,確定當(dāng)前jdk的路徑很簡單  開始 -》運(yùn)行-》rmiregistry 看看這個(gè)DOS窗口標(biāo)題的路徑,就是你當(dāng)前系統(tǒng)默認(rèn)jdk的路徑了

客戶端正常運(yùn)行以后,就會出現(xiàn)以下結(jié)果:
Hello world !!


這些字符是通過RMI調(diào)用遠(yuǎn)程服務(wù)器的類返回的結(jié)果