概述
RMI 是 Java 提供的一個(gè)完善的簡(jiǎn)單易用的遠(yuǎn)程方法調(diào)用框架,采用客戶/服務(wù)器通信方式,在服務(wù)器上部署了提供各種服務(wù)的遠(yuǎn)程對(duì)象,客戶端請(qǐng)求訪問服務(wù)器上遠(yuǎn)程對(duì)象的方法,它要求客戶端與服務(wù)器端都是 Java 程序
RMI 框架采用代理來負(fù)責(zé)客戶與遠(yuǎn)程對(duì)象之間通過 Socket 進(jìn)行通信的細(xì)節(jié)。RMI 框架為遠(yuǎn)程對(duì)象分別生成了客戶端代理和服務(wù)器端代理。位于客戶端的代理必被稱為存根(Stub),位于服務(wù)器端的代理類被稱為骨架(Skeleton)
當(dāng)客戶端調(diào)用遠(yuǎn)程對(duì)象的一個(gè)方法時(shí),實(shí)際上是調(diào)用本地存根對(duì)象的相應(yīng)方法。存根對(duì)象與遠(yuǎn)程對(duì)象具有同樣的接口。存根采用一種與平臺(tái)無關(guān)的編碼方式,把方法的參數(shù)編碼為字節(jié)序列,這個(gè)編碼過程被稱為參數(shù)編組。RMI 主要采用Java 序列化機(jī)制進(jìn)行參數(shù)編組。存根把以下請(qǐng)求信息發(fā)送給服務(wù)器:
- 被訪問的遠(yuǎn)程對(duì)象的名字
- 被調(diào)用的方法的描述
- 編組后的參數(shù)的字節(jié)序列
服務(wù)器端接收到客戶端的請(qǐng)求信息,然后由相應(yīng)的骨架對(duì)象來處理這一請(qǐng)求信息,骨架對(duì)象執(zhí)行以下操作:
- 反編組參數(shù),即把參數(shù)的字節(jié)序列反編碼為參數(shù)
- 定位要訪問的遠(yuǎn)程對(duì)象
- 調(diào)用遠(yuǎn)程對(duì)象的相應(yīng)方法
- 獲取方法調(diào)用產(chǎn)生的返回值或者異常,然后對(duì)它進(jìn)行編組
- 把編組后的返回值或者異常發(fā)送給客戶
客戶端的存根接收到服務(wù)器發(fā)送過來的編組后的返回值或者異常,再對(duì)它進(jìn)行反編組,就得到調(diào)用遠(yuǎn)程方法的返回結(jié)果
JDK5.0 之后,RMI 框架會(huì)在運(yùn)行時(shí)自動(dòng)為運(yùn)程對(duì)象生成動(dòng)態(tài)代理類(包括存根和骨架類),從而更徹底地封裝了 RMI 框架的實(shí)現(xiàn)細(xì)節(jié),簡(jiǎn)化了 RMI 框架的使用方式
創(chuàng)建 RMI 應(yīng)用
創(chuàng)建一個(gè) RMI 應(yīng)用包括以下步驟:
- 創(chuàng)建遠(yuǎn)程接口:繼承 java.rmi.Remote 接口
- 創(chuàng)建遠(yuǎn)程類:實(shí)現(xiàn)遠(yuǎn)程接口
- 創(chuàng)建服務(wù)器程序:負(fù)責(zé)在 RMI 注冊(cè)器中注冊(cè)遠(yuǎn)程對(duì)象
- 創(chuàng)建客戶程序:負(fù)貴定位遠(yuǎn)程對(duì)象,并且調(diào)用遠(yuǎn)程對(duì)象的方法
1. 創(chuàng)建遠(yuǎn)程接口
遠(yuǎn)程接口中聲明了可以被客戶程序訪問的遠(yuǎn)程方法,并直接或間接繼承 java.rmi.Remote 接口
import java.rmi.*;
public interface HelloService extends Remote {
public String echo(String msg) throws RemoteException;
}
2. 創(chuàng)建遠(yuǎn)程類
遠(yuǎn)程類必須實(shí)現(xiàn)一個(gè)遠(yuǎn)程接口,此外,為了使遠(yuǎn)程類的實(shí)例變成能為遠(yuǎn)程客戶提供服務(wù)的遠(yuǎn)程對(duì)象,可通過以下兩種途徑之一把它導(dǎo)出為遠(yuǎn)程對(duì)象:
-
使遠(yuǎn)程類繼承 java.rmi.server.UnicastRemoteObjcct 類,并且遠(yuǎn)程類的構(gòu)構(gòu)方法必聲明拋出 RemoteException
import java.rmi.*; import java.rmi.server.UnicastRemoteObjoct; public class HelloServlceImpl extends UnicagtRemoteObject implements HelloService { private String name; public HelloServicelmpl(String name) throws RemoteException { this.name = name; } public String echo(String msg) throws RemoteException { System.out.println(name + ":測(cè)用echo()方法"); return "echo;" + msg + " from" + name; } }
-
如果一個(gè)遠(yuǎn)程類已經(jīng)繼承了其他類,無法再繼承 UnicastRemoteObiect 類,那么可以在構(gòu)造方法中調(diào)用 UnicastRemoteObject 類的靜態(tài) expotObject 方法,同樣,遠(yuǎn)程類的構(gòu)造方法也必須聲明拋出 RemoteException
public class HelloServlceImpl extends OtherClass implements HelloService { private String name; public HelloServicelmpl(String name) throws RemoteException { this.name = name; //參數(shù) port 指定監(jiān)聽的端口,如果取值為0,就表示監(jiān)聽任意一個(gè)匿名端口 UnicagtRemoteObject.exportobject(this, 0); } public String echo(String msg) throws RemoteException { System.out.println(name + ":測(cè)用echo()方法"); return "echo;" + msg + " from" + name; } }
3. 創(chuàng)建服務(wù)器程序
RMI 采用一種命名服務(wù)機(jī)制來使得客戶程序可以找到服務(wù)器上的一個(gè)遠(yuǎn)程對(duì)象,RMI注冊(cè)器提供這種命名服務(wù)。好比電話查詢系統(tǒng),那些希望對(duì)外公開聯(lián)系方式的單位先到查詢系統(tǒng)登記,當(dāng)客戶想知道某個(gè)單位的聯(lián)系方式時(shí),只需向查詢系統(tǒng)提供單位的名字,查詢系統(tǒng)就會(huì)返回該單位的聯(lián)系方式
啟動(dòng) RMI 注冊(cè)器有兩種方式。一種方式是直接運(yùn)行 rmiregistry.exe 程序,在 JDK 的安裝目錄的 bin 子目錄下有一個(gè) rmiregistry.exe 程序,它是提供命名服務(wù)的注冊(cè)器程序。盡管 rmiregistry 注冊(cè)器程序也可以單獨(dú)運(yùn)行在一個(gè)主機(jī)上,但出于安全的原因,通常讓 rmiregistry 注冊(cè)器程序與服務(wù)器程序運(yùn)行在同一個(gè)主機(jī)上
啟動(dòng) RMI 注冊(cè)器的另一種方式是在服務(wù)器程序中調(diào)用 java.rmiregistry.LocateRegistry 類的靜態(tài)方法 createRegistry()
//默認(rèn)的監(jiān)聽路口為1099
Registry registry = LocateRegistry.createRegigtry(1099);
向注冊(cè)器注冊(cè)遠(yuǎn)程對(duì)象有三種方式:
//創(chuàng)建遠(yuǎn)程對(duì)象
HelloService service1 = new HelloServiceImpl("service1");
//方式1:調(diào)用 java.i.registry.Registy 接口的 bind 或 rebind 方法
Registry registry = LocateRegistry.createRegistry(1099);
registry.rebind("HelloService1", service1);
//方式2:調(diào)用命名服務(wù)類 java.rmi.Naming 的 bind 或 rebind 方法
Naming.rebind("HelloService1", service1);
//方式3:調(diào)用 JNDI API 的 javax.naming.Context 接口的 bind 或rebind 方法
Context namingContext = new InitialContext();
namingContext.rebind("rmi:HelloService1", service1);
下例的 SimpleServer 類創(chuàng)建了兩個(gè) HelloServicelmpl 遠(yuǎn)程對(duì)象,接著創(chuàng)建并啟動(dòng) RMI 注冊(cè)器,然后把兩個(gè)遠(yuǎn)程對(duì)象注冊(cè)到 RMI 注冊(cè)器
import java.rmi.*;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class SimpleServer {
public static void main( String args[]) {
try {
HelloService service1 = new HelloServiceImpl("service1");
HelloService service2 = new HelloServiceImpl("service2");
//創(chuàng)建并啟動(dòng)注冊(cè)器
Registry registry = LocateRegistry.createRegistry(1099);
//注冊(cè)遠(yuǎn)程對(duì)象
regigtry.rebind("HelloService1", service1);
regigtry.rebind("HelloService2", service2);
} catch(Exception e) {
e.printStackTrace();
}
}
}
關(guān)于向 RMI 注冊(cè)器注冊(cè)遠(yuǎn)程對(duì)象,需要注意的是,遠(yuǎn)程對(duì)象即使沒有在注冊(cè)器中注冊(cè),也可被遠(yuǎn)程訪問
4. 創(chuàng)建客戶程序
下例的 SimpleClient 類先獲得遠(yuǎn)程對(duì)象的存根對(duì)象,接著調(diào)用它的遠(yuǎn)程方法
import java.rmi.*;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class SimpleClient {
public static void main(String args[]) {
try {
//返回本地主機(jī)的RMI注冊(cè)器對(duì)象,參數(shù)port指定RMI注冊(cè)器監(jiān)聽的端口
Registry registry = LocateRegistry.getRegistry(1099);
//查找對(duì)象,返回與參數(shù)name指定的名字所綁定的對(duì)象
//返回的是一個(gè)名為"com.sun.proxy.$Proxy0"的動(dòng)態(tài)代理類的實(shí)例
HelloService service1 = (HelloService) registry.lookup("HelloService1");
HelloService service2 = (HelloService) registry.lookup("HelloService2");
System.out.println(service1.echo("hello"));
System.out.println(service2.echo("hello"));
}
}
}
遠(yuǎn)程方法中的參數(shù)與返回值傳遞
當(dāng)客戶端調(diào)用服務(wù)器端的遠(yuǎn)程對(duì)象的方法時(shí),客戶端會(huì)向服務(wù)器端傳遞參數(shù),服務(wù)器端則會(huì)向客戶端傳遞返回值。RMI 規(guī)范對(duì)參數(shù)以及返回值的傳遞的規(guī)定如下所述:
- 只有基本類型的數(shù)據(jù)、遠(yuǎn)程對(duì)象以及可序列化的對(duì)象才可以被作為參數(shù)或者返回值進(jìn)行傳遞
- 如果參數(shù)或返回值是一個(gè)遠(yuǎn)程對(duì)象,那么把它的存根對(duì)象傳遞到接收方。也就是說接收方得到的是遠(yuǎn)程對(duì)象的存根對(duì)象
- 如果參數(shù)或返回值是可序列化對(duì)象,那么直接傳遞該對(duì)象的序列化數(shù)據(jù)。也就是說接收方得到的是發(fā)送方的可序列化對(duì)象的復(fù)制品
- 如果參數(shù)或返回值是基本類型的數(shù)據(jù),那么直接傳遞該數(shù)據(jù)的序列化數(shù)據(jù)。也就是說,接收方得到的是發(fā)送方的基本類型的數(shù)據(jù)的復(fù)制品
分布式垃圾收集
在 Java 虛擬機(jī)中,對(duì)于一個(gè)本地對(duì)象,只要不被本地 Java 虛擬機(jī)內(nèi)的任何變量引用,它就會(huì)結(jié)束生命周期,可以被垃圾回收器回收。而對(duì)于一個(gè)遠(yuǎn)程對(duì)象,不僅會(huì)被本地 Java 虛擬機(jī)內(nèi)的變量引用,還會(huì)被遠(yuǎn)程引用
服務(wù)器端的一個(gè)遠(yuǎn)程對(duì)象受到三種引用:
- 服務(wù)器端的一個(gè)本地對(duì)象持有它的本地引用
- 這個(gè)遠(yuǎn)程對(duì)象已經(jīng)被注冊(cè)到 RMI 注冊(cè)器,可以理解為,RMI 注冊(cè)器持有它的引用
- 客戶端獲得了這個(gè)遠(yuǎn)程對(duì)象的存根對(duì)象,可以理解為,客戶端持有它的遠(yuǎn)程引用
RMI 框架采用分布式垃圾收集機(jī)制來管理遠(yuǎn)程對(duì)象的生命周期,當(dāng)一個(gè)遠(yuǎn)程對(duì)象不受到任何本地引用和遠(yuǎn)程引用時(shí),這個(gè)遠(yuǎn)程對(duì)象才會(huì)結(jié)束生命周期,并且可以被本地 Java 虛擬機(jī)的垃圾回收器回收。
服務(wù)器端如何知道客戶端持有一個(gè)遠(yuǎn)程對(duì)象的遠(yuǎn)程引用呢?當(dāng)客戶端獲得了一個(gè)服務(wù)器端的遠(yuǎn)程對(duì)象的存根后,就會(huì)向服務(wù)器發(fā)送一條租約通知,告訴服務(wù)器自己持有這個(gè)遠(yuǎn)程對(duì)象的引用了。客戶端對(duì)這個(gè)遠(yuǎn)程對(duì)象有一個(gè)租約期限,默認(rèn)值為 600000ms。當(dāng)至達(dá)了租約期限的一半時(shí)間,客戶如果還持有遠(yuǎn)程引用,就會(huì)再次向服務(wù)器發(fā)送租約通知。客戶端不斷在給定的時(shí)間間隔中向服務(wù)器發(fā)送租約通知,從而使腸務(wù)器知道客戶端一直持有遠(yuǎn)程對(duì)象的引用。如果在租約到期后,服務(wù)器端沒有繼續(xù)收到客戶端的新的租約通知,服務(wù)器端就會(huì)認(rèn)為這個(gè)客戶已經(jīng)不再持有遠(yuǎn)程對(duì)象的引用了
動(dòng)態(tài)加載
遠(yuǎn)程對(duì)象一般分布在服務(wù)器端,當(dāng)客戶端試圖調(diào)用遠(yuǎn)程對(duì)象的方法時(shí),如果在客戶端還不存在遠(yuǎn)程對(duì)象所依賴的類文件,比如遠(yuǎn)程方法的參數(shù)和返回值對(duì)應(yīng)的類文件,客戶就會(huì)從 java.rmi.server.codebase 系統(tǒng)屬性指定的位貿(mào)動(dòng)態(tài)加載該類文件
同樣,當(dāng)服務(wù)器端訪問客戶端的遠(yuǎn)程對(duì)象時(shí),如果服務(wù)器端不存在相關(guān)的類文件,腐務(wù)器就會(huì)從 java.rmi.server.codebase 屬性指定的位置動(dòng)態(tài)加載它們
此外,當(dāng)服務(wù)器向 RMI 注冊(cè)器注冊(cè)遠(yuǎn)程對(duì)象時(shí),注冊(cè)器也會(huì)從 java.rmi.server.codebase 屬性指定的位置動(dòng)態(tài)加載相關(guān)的遠(yuǎn)程接口的類文件
前面的例子都是在同一個(gè) classpath 下運(yùn)行服務(wù)器程序以及客戶程序的,這些程序都能從本地 classpath 中找到相應(yīng)的類文件,因此無須從 java.rmi.server.codebase 屬性指定的位置動(dòng)態(tài)加載類。而在實(shí)際應(yīng)用中,客戶程序與服務(wù)器程序運(yùn)行在不同的主機(jī)上,因此當(dāng)客戶端調(diào)用服務(wù)器端的遠(yuǎn)程對(duì)象的方法時(shí),有可能需要從遠(yuǎn)程文件系統(tǒng)加載類文件。同樣,當(dāng)服務(wù)器端調(diào)用客戶端的遠(yuǎn)程對(duì)象的方法時(shí),也有可能從遠(yuǎn)程文件系統(tǒng)加載類文件文章來源:http://www.zghlxwxcb.cn/news/detail-485277.html
我們可以且把這些需要被加載的類的文件都集中放在網(wǎng)絡(luò)上的同一地方,啟動(dòng)時(shí)將java.rmi.server.codebase 設(shè)置為指定位置,從而實(shí)現(xiàn)動(dòng)態(tài)加載文章來源地址http://www.zghlxwxcb.cn/news/detail-485277.html
start java -Djava.rmi.server.codebase=http://www.javathinker.net/download/
到了這里,關(guān)于Java 網(wǎng)絡(luò)編程 —— RMI 框架的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!