1.簡介
Java RMI(Java Remote Method Invocation)Java遠端方法調用,是Java程式設計語言裡一種用于實作遠端過程調用的應用程式程式設計接口,使客戶機上運作的程式可以調用遠端伺服器上的對象。遠端方法調用特性使Java程式設計人員能夠在網絡環境中分布操作。RMI宗旨就是盡可能簡化遠端接口對象的使用。
遠端過程調用RPC(Remote Procedure Call)可以用于一個程序調用另一個程序(很可能在另一個遠端主機上)中的過程,進而提供了過程的分布能力。Java的RMI則在RPC的基礎上向前又邁進了一步,即提供分布式對象間的通訊。
RMI(Remote Method Invocation)為遠端方法調用,是允許運作在一個Java虛拟機的對象調用運作在另一個Java虛拟機上的對象的方法。
這兩個虛拟機可以是運作在相同計算機上的不同程序中,也可以是運作在網絡上的不同計算機中。
2 工作原理
RMI能讓一個Java程式去調用網絡中另一台計算機的Java對象的方法,那麼調用的效果就像是在本機上調用一樣。通俗的講:A機器上面有一個class,通過遠端調用,B機器調用這個class 中的方法。
RMI用是Enterprise JavaBeans的支柱,是建立分布式Java應用程式的友善途徑。
RMI的基礎是接口,RMI構架基于一個重要的原理:定義接口和定義接口的具體實作是分開的。
3. RMI包含部分:
- 遠端服務的接口定義
- 遠端服務接口的具體實作
- 樁(Stub)和架構(Skeleton)檔案
- 一個運作遠端服務的伺服器
- 一個RMI命名服務,它允許用戶端去發現這個遠端服務
- 類檔案的提供者(一個HTTP或者FTP伺服器)
- 一個需要這個遠端服務的用戶端程式
4 RMI的用途
RMI的用途是為分布式Java應用程式之間的遠端通信提供服務,提供分布式服務。目前主要應用封裝在各個J2EE項目架構中,例如Spring,EJB(Spring和EJB均封裝了RMI技術)
在Spring中實作RMI:
- ①在伺服器端定義服務的接口,定義特定的類實作這些接口;
- ②在伺服器端使用org.springframework.remoting.rmi.RmiServiceExporter類來注冊服務;
- ③在用戶端使用org.springframework.remoting.rmi.RmiProxyFactoryBean來實作遠端服務的代理功能;
- ④在用戶端定義通路與伺服器端服務接口相同的類
5 RMI的局限
RMI目前使用Java遠端消息交換協定JRMP(Java Remote Messaging Protocol)進行通信。JRMP是專為Java的遠端對象制定的協定,由于JRMP是專為Java對象制定的,是以,RMI對于用非Java語言開發的應用系統的支援不足。不能與用非Java語言書寫的對象進行通信(意思是隻支援用戶端和伺服器端都是Java程式的代碼的遠端調用,即,由于客戶機和伺服器都是使用Java編寫的,二者平台相容性的要求僅僅是雙方都運作在版本相容的Java虛拟機上)。
6 RMI調用遠端方法的參數和傳回值
當調用遠端對象上的方法時,客戶機除了可以将原始類型的資料作為參數以外,還可以将對象作為參數來傳遞,與之相對應的是傳回值,可以傳回原始類型或對象,這些都是通過Java的對象序列化(serialization)技術來實作的。(換而言之:參數或者傳回值如果是對象的話必須實作Serializable接口)
7. RMI應用程式的基本模型
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5SM1QjZlNmNwYmY5YzMlV2NjZmZkJDNwQGMyAjMlZjM48CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
8 RMI體系結構
樁/架構(Stub/Skeleton)層:用戶端的樁和伺服器端的架構;
遠端引用(remote reference)層:處理遠端引用行為;
傳送層(transport):連接配接的建立和管理,以及遠端對象的跟蹤;
9 RMI類和接口(完成一個簡單RMI需要用到的類)
9.1 Remote接口
是一個不定義方法的标記接口
public interface Remote{}
在RMI中,遠端接口聲明了可以從遠端Java虛拟機中調用的方法集。遠端接口滿足下列要求:
- 遠端接口必須直接或間接擴充Java.rmi.Remote接口,且必須聲明為public,除非用戶端與遠端接口在同一包中
- 在遠端接口中的方法在聲明時,除了要抛出與應用程式有關的一場之外,還必須包括RemoteException(或它的超類,IOExcepion或Exception)異常
- 在遠端方法聲明中,作為參數或傳回值聲明的遠端對象必須聲明為遠端接口,而非該接口的實作類。
9.2 RemoteObject抽象類
實作了Remote接口和序列化Serializable接口,它和它的子類提供RMI伺服器函數。
9.3 LocateRegistry final()類
用于獲得特定主機的引導遠端對象注冊伺服器程式的引用(即建立stub),或者建立能在特定端口接收調用的遠端對象注冊服務程式。
9.3.1 伺服器端
伺服器端負責向其他客戶機提供遠端對象服務
- 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() 傳回在此系統資料庫中綁定的名稱的數組。該數組将包含一個此系統資料庫中調用此方法時綁定的名稱快照。
9.3.2 客戶機端
向伺服器提供相應的服務請求。
Registry registry=LocateRegisty.getRegistry();
SomeService servcie=(SomeService)registry.lookup(“I serve”);
Servcie.requestService();
9.4 Naming類和Registry類類似
用戶端: - Naming.lookup(String url) - url 格式如下"rmi://localhost/"+遠端對象引用
伺服器端:
Registry registry=LocateRegistry.createRegistry(int port);
Naming.rebind(“service”,service);
9.5 RMISecurityManager類
在RMI引用程式中,如果沒有設定安全管理器,則隻能從本地類路徑加載stub和類,這可以確定應用程式不受由遠端方法調用所下載下傳的代碼侵害
在從遠端主機下載下傳代碼之前必須執行以下代碼來安裝RMISecurityManager:
System.setSecurityManager(new RMISecurityManager());
10 demo開發
為了編寫一個demo,我們分為兩部分,一部分是server端的代碼,一部分是client端的代碼,client端的代碼主要是為了使用server端的代碼。當然這個代碼是非常簡單的,隻是為了說明問題,現實的使用比較複雜的。
10.1 目的
建立一個server端的java project,包含遠端端的代碼,定義接口,定義接口實作,然後建立一個client端的java project,通過RMI使用遠端服務中的方法。
10.2 遠端服務代碼
10.2.1. 遠端服務的接口定義
第一步就是建立和編譯服務接口的Java代碼。這個接口定義了所有的提供遠端服務的功能,下面是源程式:
import java.rmi.Remote;
import java.rmi.RemoteException;
import p20190202.rmi.bean.Account;
public interface UserManagerInterface extends Remote{
public String getUserName() throws RemoteException;
public Account getAdminAccount() throws RemoteException;
}
接口必須繼承Remote類,每一個定義地方法都要抛出RemoteException異常對象。
10.2.2 接口的具體實作
第二步就是對于上面的接口進行實作:
import java.rmi.RemoteException;
import p20190202.rmi.bean.Account;
import p20190202.rmi.stub.UserManagerInterface;
public class UserManagerImpl implements UserManagerInterface{
private static final long serialVersionUID = -3111492742628447261L;
public UserManagerImpl() throws RemoteException {
super();
}
@Override
public String getUserName() throws RemoteException {
return "TT";
}
@Override
public Account getAdminAccount() throws RemoteException {
Account account = new Account();
account.setUsername("TT");
account.setPassword("123456");
return account;
}
}
10.2.3. 定義bean
實作implements Serializable序列化接口。也就是可以在client和server端進行傳輸的可序列化對象。
public class Account {
private static final long serialVersionUID = -1858518369668584532L;
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
10.2.4. 定義server端的主程式入口
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import p20190202.rmi.rmiserver.UserManagerImpl;
import p20190202.rmi.stub.UserManagerInterface;
public class Entry {
public static void main(String[] args) throws RemoteException {
UserManagerImpl userManager = new UserManagerImpl();
UserManagerInterface userManagerInterface =
(UserManagerInterface) UnicastRemoteObject.exportObject(userManager, 0);
// Bind the remote object's stub in the registry
Registry registry = LocateRegistry.createRegistry(2002);
registry.rebind("userManager", userManagerInterface);
System.out.println("server is ready");
}
}
10.3 client端代碼
1.把Server端的Account類和接口UserManagerInterface導出Export成jar包,命名為:RmiServerInterface.jar。導入到client中。
2.項目——右鍵——Export——java——jar file——next——選擇Account類和接口UserManagerInterface——命名為:RmiServerInterface.jar如下圖:
- 建立一個java Project,導入jar包,編寫用戶端代碼。
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import p20190202.rmi.stub.UserManagerInterface;
public class ClientEntry {
public static void main(String[] args) {
try {
Registry registry = LocateRegistry.getRegistry("localhost", 2004);
UserManagerInterface userManagerInterface = (UserManagerInterface) registry.lookup("userManager");
System.out.println("使用者名是"+userManagerInterface.getAdminAccount().getUsername()+"密碼"+userManagerInterface.getAdminAccount().getPassword());
} catch (RemoteException e) {
e.printStackTrace();
} catch (NotBoundException e) {
e.printStackTrace();
}
}
}
4 先運作伺服器端代碼,然後運作用戶端代碼,就會顯示運作結果,用戶端可以運作多次,每次都可以取得伺服器端的對象。如果要再次運作用戶端代碼就需要更改端口号,如果不更改就會顯示端口号被占用。