【前面的話】
最近過的好舒服,每天過的感覺很充實,一些生活和工作的技巧注意了就會發現,其實生活也是可以過的如此的有滋有味,滿足現在的狀況,並且感覺很幸福。
學習java RMI的原因是最近在使用dubbo框架做一個系統,所以對這java RMI進行學習,做一些筆記,基礎性文章,選擇性閱讀。
【定義】
Java RMI:Java遠程方法調用,即Java RMI(Java Remote Method Invocation)是Java編程語言里,一種用於實現遠程過程調用的應用程序編程接口。它使客戶機上運行的程序可以調用遠程服務器上的對象。遠程方法調用特性使Java編程人員能夠在網絡環境中分布操作。RMI全部的宗旨就是盡可能簡化遠程接口對象的使用。
我們知道遠程過程調用(Remote Procedure Call, RPC)可以用於一個進程調用另一個進程(很可能在另一個遠程主機上)中的過程,從而提供了過程的分布能力。Java 的 RMI 則在 RPC 的基礎上向前又邁進了一步,即提供分布式對象間的通訊。
RMI(Remote Method Invocation)為遠程方法調用,是允許運行在一個Java虛擬機的對象調用運行在另一個Java虛擬機上的對象的方法。
這兩個虛擬機可以是運行在相同計算機上的不同進程中,也可以是運行在網絡上的不同計算機中。
【JavaRMI】
一、工作原理
RMI能讓一個Java程序去調用網絡中另一台計算機的Java對象的方法,那么調用的效果就像是在本機上調用一樣。通俗的講:A機器上面有一個class,通過遠程調用,B機器調用這個class 中的方法。
RMI,遠程方法調用(Remote Method Invocation)是Enterprise JavaBeans的支柱,是建立分布式Java應用程序的方便途徑。RMI是非常容易使用的,但是它非常的強大。
RMI的基礎是接口,RMI構架基於一個重要的原理:定義接口和定義接口的具體實現是分開的。下面我們通過具體的例子,建立一個簡單的遠程計算服務和使用它的客戶程序
二、RMI包含部分:
- 遠程服務的接口定義
- 遠程服務接口的具體實現
- 樁(Stub)和框架(Skeleton)文件
- 一個運行遠程服務的服務器
- 一個RMI命名服務,它允許客戶端去發現這個遠程服務
- 類文件的提供者(一個HTTP或者FTP服務器)
- 一個需要這個遠程服務的客戶端程序
三、RMI的用途?
RMI的用途是為分布式Java應用程序之間的遠程通信提供服務,提供分布式服務。
目前主要應用時封裝在各個J2EE項目框架中,例如Spring,EJB(Spring和EJB均封裝了RMI技術)
在Spring中實現RMI:
①在服務器端定義服務的接口,定義特定的類實現這些接口;
②在服務器端使用org.springframework.remoting.rmi.RmiServiceExporter類來注冊服務;
③在客戶端使用org.springframework.remoting.rmi.RmiProxyFactoryBean來實現遠程服務的代理功能;
④在客戶端定義訪問與服務器端服務接口相同的類
四、RMI的局限?
RMI目前使用Java遠程消息交換協議JRMP(Java Remote Messaging Protocol)進行通信。JRMP是專為Java的遠程對象制定的協議,由於JRMP是專為Java對象制定的,因此,RMI對於用非Java語言開發的應用系統的支持不足。不能與用非Java語言書寫的對象進行通信(意思是只支持客戶端和服務器端都是Java程序的代碼的遠程調用)。
五、RMI的使用局限?
由於客戶機和服務器都是使用Java編寫的,二者平台兼容性的要求僅僅是雙方都運行在版本兼容的Java虛擬機上。
六、RMI調用遠程方法的參數和返回值
當調用遠程對象上的方法時,客戶機除了可以將原始類型的數據作為參數一外,還可以將對象作為參數來傳遞,與之相對應的是返回值,可以返回原始類型或對象,這些都是通過Java的對象序列化(serialization)技術來實現的。(換而言之:參數或者返回值如果是對象的話必須實現Serializable接口)
七、 RMI應用程序的基本模型
八、RMI體系結構

樁/框架(Stub/Skeleton)層:客戶端的樁和服務器端的框架;
遠程引用(remote reference)層:處理遠程引用行為
傳送層(transport):連接的建立和管理,以及遠程對象的跟蹤
九、 RMI類和接口(完成一個簡單RMI需要用到的類)。



(一) Remote接口:是一個不定義方法的標記接口
Public interface Remote{}
在RMI中,遠程接口聲明了可以從遠程Java虛擬機中調用的方法集。遠程接口滿足下列要求:
1、遠程接口必須直接或間接擴展Java.rmi.Remote接口,且必須聲明為public,除非客戶端於遠程接口在同一包中
2、在遠程接口中的方法在聲明時,除了要拋出與應用程序有關的一場之外,還必須包括RemoteException(或它的超類,IOExcepion或Exception)異常
3、在遠程方法聲明中,作為參數或返回值聲明的遠程對象必須聲明為遠程接口,而非該接口的實現類。
(二) RemoteObject抽象類實現了Remote接口和序列化Serializable接口,它和它的子類提供RMI服務器函數。
(三) LocateRegistry final()類用於獲得特定主機的引導遠程對象注冊服務器程序的引用(即創建stub),或者創建能在特定端口接收調用的遠程對象注冊服務程序。
服務器端:向其他客戶機提供遠程對象服務
SomeService servcie=……;//遠程對象服務
- Registry registry=LocateRegisty.getRegistry();//Registry是個接口,他繼承了Remote,此方法返回本地主機在默認注冊表端口 1099 上對遠程對象
Registry的引用。 - getRegistry(int port) 返回本地主機在指定 port 上對遠程對象 Registry 的引用;
- getRegistry(String host) 返回指定
host在默認注冊表端口 1099 上對遠程對象Registry的引用; - getRegistry(String host, int port) 返回指定的
host和port上對遠程對象 Registry 的引用 - registry.bind(“I serve”,service);// bind(String name,Remote obj) 綁定對此注冊表中指定 name 的遠程引用。name : 與該遠程引用相關的名稱 obj : 對遠程對象(通常是一個 stub)的引用
- unbind(String name)移除注冊表中指定name的綁定。
- rebind(String name,Remote obj)重新綁定,如果name已存在,但是Remote不一樣則替換,如果Remote一樣則丟棄現有的綁定
- lookup(String name) 返回注冊表中綁定到指定 name 的遠程引用,返回Remote
- String[] list() 返回在此注冊表中綁定的名稱的數組。該數組將包含一個此注冊表中調用此方法時綁定的名稱快照。
客戶機端:向服務器提供相應的服務請求。
Registry registry=LocateRegisty.getRegistry();
SomeService servcie=(SomeService)registry.lookup(“I serve”);
Servcie.requestService();
(四) Naming類和Registry類類似。
客戶端:
Naming.lookup(String url)
url 格式如下"rmi://localhost/"+遠程對象引用
服務器端:
Registry registry=LocateRegistry.createRegistry(int port);
Naming.rebind(“service”,service);
(五) RMISecurityManager類
在RMI引用程序中,如果沒有設置安全管理器,則只能從本地類路徑加載stub和類,這可以確保應用程序不受由遠程方法調用所下載的代碼侵害
在從遠程主機下載代碼之前必須執行以下代碼來安裝RMISecurityManager:
System.setSecurityManager(new RMISecurityManager());
十、demo開發
為了編寫一個demo,我們分為兩部分,一部分是server端的代碼,一部分是client端的代碼,client端的代碼主要是為了使用server端的代碼。當然這個代碼是非常簡單的,只是為了說明問題,現實的使用會使比較復雜的。
(一) 我們的目的
建立一個server端的java project,包含遠程端的代碼,定義接口,定義接口實現,然后在建立一個client端的java project,通過RMI使用遠端服務中的方法。
(二) 我們的代碼結構

(三) 遠程服務代碼
1. 遠程服務的接口定義
第一步就是建立和編譯服務接口的Java代碼。這個接口定義了所有的提供遠程服務的功能,下面是源程序:
UserManagerInterface.java
1 package cn.com.tt.rmiserver.stub; 2 3 import java.rmi.Remote; 4 import java.rmi.RemoteException; 5 6 import cn.com.tt.rmiserver.bean.Account; 7 8 public interface UserManagerInterface extends Remote{ 9 public String getUserName() throws RemoteException; 10 public Account getAdminAccount() throws RemoteException; 11 }
接口必須繼承Remote類,每一個定義地方法都要拋出RemoteException異常對象。
2. 接口的具體實現
第二步就是對於上面的接口進行實現:
UserManagerImp.java
1 package cn.com.tt.rmiserver; 2 3 import java.rmi.RemoteException; 4 5 import cn.com.tt.rmiserver.stub.UserManagerInterface; 6 import cn.com.tt.rmiserver.bean.Account; 7 8 public class UserManagerImp implements UserManagerInterface { 9 public UserManagerImp() throws RemoteException { 10 11 } 12 private static final long serialVersionUID = -3111492742628447261L; 13 14 public String getUserName() throws RemoteException{ 15 return "TT"; 16 } 17 public Account getAdminAccount() throws RemoteException{ 18 Account account=new Account(); 19 account.setUsername("TT"); 20 account.setPassword("123456"); 21 return account; 22 } 23 }
3. 定義一個bean,實現implements Serializable序列化接口。也就是可以在client和server端進行傳輸的可序列化對象。
Account.java
1 package cn.com.tt.rmiserver.bean; 2 3 import java.io.Serializable; 4 5 public class Account implements Serializable,Cloneable{ 6 private static final long serialVersionUID = -1858518369668584532L; 7 private String username; 8 private String password; 9 10 public String getUsername() { 11 return username; 12 } 13 public void setUsername(String username) { 14 this.username = username; 15 } 16 public String getPassword() { 17 return password; 18 } 19 public void setPassword(String password) { 20 this.password = password; 21 } 22 }
4. 定義server端的主程序入口。
Entry.java
1 package cn.com.tt.rmiserver.entry; 2 3 import java.rmi.AlreadyBoundException; 4 import java.rmi.RemoteException; 5 import java.rmi.registry.LocateRegistry; 6 import java.rmi.registry.Registry; 7 import java.rmi.server.UnicastRemoteObject; 8 9 import cn.com.tt.rmiserver.UserManagerImp; 10 import cn.com.tt.rmiserver.stub.UserManagerInterface; 11 12 public class Entry { 13 public static void main(String []args) throws AlreadyBoundException, RemoteException{ 14 UserManagerImp userManager=new UserManagerImp(); 15 UserManagerInterface userManagerI=(UserManagerInterface)UnicastRemoteObject.exportObject(userManager,0); 16 // Bind the remote object's stub in the registry 17 Registry registry = LocateRegistry.createRegistry(2002); 18 19 registry.rebind("userManager", userManagerI); 20 System.out.println("server is ready"); 21 } 22 }
(四) client端代碼
- 把Server端的Account類和接口UserManagerInterface 導出Export成jar包,命名為:RmiServerInterface.jar。導入到client中。
- 項目——右鍵——Export——java——jar file——next——選擇Account類和接口UserManagerInterface——命名為:RmiServerInterface.jar如下圖:

3. 新建一個java Project,導入jar包,編寫客戶端代碼。
4. 代碼
ClientEntry.java
1 package weiblog.rmi; 2 3 import java.rmi.NotBoundException; 4 import java.rmi.RemoteException; 5 import java.rmi.registry.LocateRegistry; 6 import java.rmi.registry.Registry; 7 8 import cn.com.tt.rmiserver.stub.UserManagerInterface; 9 10 public class ClientEntry { 11 12 public static void main(String []args){ 13 14 try { 15 Registry registry = LocateRegistry.getRegistry("localhost",2004); 16 UserManagerInterface userManager = (UserManagerInterface)registry.lookup("userManager"); 17 System.out.println("用戶名是"+userManager.getAdminAccount().getUsername() 18 +"密碼"+userManager.getAdminAccount().getPassword()); 19 } catch (RemoteException e) { 20 // TODO Auto-generated catch block 21 e.printStackTrace(); 22 } catch (NotBoundException e) { 23 // TODO Auto-generated catch block 24 e.printStackTrace(); 25 } 26 27 } 28 29 }
5. 先運行服務器端代碼, 然后運行客戶端代碼,就會顯示運行結果,客戶端可以運行多次,每次都可以取得服務器端的對象。如果要再次運行客戶端代碼就需要更改端口號,如果不更改就會顯示端口號被占用。
【參考資料】
1. demo參考的下面文章:
RMI網絡編程開發之二 如何搭建基於JDK1.5的分布式JAVA RMI 程序
2. 理論知識參考的下面文章:
【后面的話】
快要放假了多么值得高興。
時至今日都是我咎由自取,學java那是自找,與任何人無關。大學生活過的平順,造就了我輕信java,編寫眾多bug的脾氣,導致今日岌岌可危的地步,我今天願意承擔一切后果。其實,我很感激你們讓我在測試的時候發現bug,而不是到了生產環境,我必須重新梳理我所造成的bug,坦然面對每個bug。我,在學java的路上編寫了大量的bug。我辜負了c和c++,辜負了C#,辜負了程序員和碼農的稱呼,辜負了所有以為我可以寫出漂亮代碼的人。對不起,請接收我發自內心的歉意和懺悔。晚上和周末本來有一個溫暖和美的生活,可是這一切被我學習java給打破了,我學java的行為不配得到原諒,我造成的無休止的bug現狀也難以修改,但我還是想修改,我必須修改,這是我今日之后的生活。至於我自己,已咎由自取,願日后不在產生bug。java程序員。——我真是越來越喜歡自黑了。哈哈,讓我笑一會。
——TT
