RMI 介紹
RMI (Remote Method Invocation) 模型是一種分布式對(duì)象應(yīng)用,使用 RMI 技術(shù)可以使一個(gè) JVM 中的對(duì)象,調(diào)用另一個(gè) JVM 中的對(duì)象方法并獲取調(diào)用結(jié)果。這里的另一個(gè) JVM 可以在同一臺(tái)計(jì)算機(jī)也可以是遠(yuǎn)程計(jì)算機(jī)。因此,RMI 意味著需要一個(gè) Server 端和一個(gè) Client 端。
Server 端通常會(huì)創(chuàng)建一個(gè)對(duì)象,并使之可以被遠(yuǎn)程訪問(wèn)。
這個(gè)對(duì)象被稱為遠(yuǎn)程對(duì)象。Server 端需要注冊(cè)這個(gè)對(duì)象可以被 Client 遠(yuǎn)程訪問(wèn)。
Client 端調(diào)用可以被遠(yuǎn)程訪問(wèn)的對(duì)象上的方法,Client 端就可以和 Server 端進(jìn)行通信并相互傳遞信息。
說(shuō)到這里,是不是發(fā)現(xiàn)使用 RMI 在構(gòu)建一個(gè)分布式應(yīng)用時(shí)十分方便,它和 RPC 一樣可以實(shí)現(xiàn)分布式應(yīng)用之間的互相通信,甚至和現(xiàn)在的微服務(wù)思想都十分類似。
RMI 工作原理
正所謂 “知其然知其所以然”,在開(kāi)始編寫(xiě) RMI 代碼之前,有必要了解一下 RMI 的工作原理,RMI 中 Client 端是和 Server 端是如何通信的呢?
下圖的可以幫助我們理解RMI 的工作流程。
從圖中可以看到,Client 端有一個(gè)被稱 Stub 的東西,有時(shí)也會(huì)被成為存根,它是 RMI Client 的代理對(duì)象,Stub 的主要功能是請(qǐng)求遠(yuǎn)程方法時(shí)構(gòu)造一個(gè)信息塊,RMI 協(xié)議會(huì)把這個(gè)信息塊發(fā)送給 Server 端。
這個(gè)信息塊由幾個(gè)部分組成:
- 遠(yuǎn)程對(duì)象標(biāo)識(shí)符。
- 調(diào)用的方法描述。
- 編組后的參數(shù)值(RMI協(xié)議中使用的是對(duì)象序列化)。
既然 Client 端有一個(gè) Stub 可以構(gòu)造信息塊發(fā)送給 Server 端,那么 Server 端必定會(huì)有一個(gè)接收這個(gè)信息快的對(duì)象,稱為 Skeleton 。
它主要的工作是:
- 解析信息快中的調(diào)用對(duì)象標(biāo)識(shí)符和方法描述,在 Server 端調(diào)用具體的對(duì)象方法。
- 取得調(diào)用的返回值或者異常值。
- 把返回值進(jìn)行編組,返回給客戶端 Stub.
到這里,一次從 Client 端對(duì) Server 端的調(diào)用結(jié)果就可以獲取到了。
RMI 開(kāi)發(fā)
通過(guò)上面的介紹,知道了 RMI 的概念以及 RMI 的工作原理,下面介紹 RMI 的開(kāi)發(fā)流程。
這里會(huì)通過(guò)一個(gè)場(chǎng)景進(jìn)行演示,假設(shè) Client 端需要查詢用戶信息,而用戶信息存在于 Server 端,所以在 Server 端開(kāi)放了 RMI 協(xié)議接口供客戶端調(diào)用查詢。
RMI Server
Server 端主要是構(gòu)建一個(gè)可以被傳輸?shù)念?User,一個(gè)可以被遠(yuǎn)程訪問(wèn)的類 UserService,同時(shí)這個(gè)對(duì)象要注冊(cè)到 RMI 開(kāi)放給客戶端使用。
1.定義服務(wù)器接口(需要繼承 Remote 類,方法需要拋出 RemoteException)。
package com.wdbyte.rmi.server;
import java.rmi.Remote;
import java.rmi.RemoteException;
/**
* RMI Server
*
* @author www.wdbyte.com
* @date 2021/05/08
*/
public interface UserService extends Remote {
/**
* 查找用戶
*
* @param userId
* @return
* @throws RemoteException
*/
User findUser(String userId) throws RemoteException;
}
User 對(duì)象在步驟 3 中定義。
2.實(shí)現(xiàn)服務(wù)器接口(需要繼承 UnicastRemoteObject 類,實(shí)現(xiàn)定義的接口)。
package com.wdbyte.rmi.server;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
/**
* @author www.wdbyte.com
* @date 2021/05/08
*/
public class UserServiceImpl extends UnicastRemoteObject implements UserService {
protected UserServiceImpl() throws RemoteException {
}
@Override
public User findUser(String userId) throws RemoteException {
// 加載在查詢
if ("00001".equals(userId)) {
User user = new User();
user.setName("金庸");
user.setAge(100);
user.setSkill("寫(xiě)作");
return user;
}
throw new RemoteException("查無(wú)此人");
}
}
3.定義傳輸?shù)膶?duì)象,傳輸?shù)膶?duì)象需要實(shí)現(xiàn)序列化(Serializable)接口。
需要傳輸?shù)念愐欢ㄒ獙?shí)現(xiàn)序列化接口,不然傳輸時(shí)會(huì)報(bào)錯(cuò)。IDEA 中如何生成 serialVersionUID,在文章末尾也附上了簡(jiǎn)單教程。
package com.wdbyte.rmi.server;
import java.io.Serializable;
/**
*
* @author www.wdbyte.com
* @date 2021/05/08
*/
public class User implements Serializable {
private static final long serialVersionUID = 6490921832856589236L;
private String name;
private Integer age;
private String skill;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSkill() {
return skill;
}
public void setSkill(String skill) {
this.skill = skill;
}
@Override
public String toString() {
return "User{" +
"name='" + name + ''' +
", age=" + age +
", skill='" + skill + ''' +
'}';
}
}
4.注冊(cè)( rmiregistry)遠(yuǎn)程對(duì)象,并啟動(dòng)服務(wù)端程序。
服務(wù)端綁定了 UserService 對(duì)象作為遠(yuǎn)程訪問(wèn)的對(duì)象,啟動(dòng)時(shí)端口設(shè)置為 1900。
package com.wdbyte.rmi.server;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
/**
* RMI Server 端
*
* @author https://www.wdbyte.com
* @date 2021/05/08
*/
public class RmiServer {
public static void main(String[] args) {
try {
UserService userService = new UserServiceImpl();
LocateRegistry.createRegistry(1900);
Naming.rebind("rmi://localhost:1900/user", userService);
System.out.println("start server,port is 1900");
} catch (Exception e) {
e.printStackTrace();
}
}
}
RMI Client
相比 Server 端,Client 端就簡(jiǎn)單的多。直接引入可遠(yuǎn)程訪問(wèn)和需要傳輸?shù)念?,通過(guò)端口和 Server 端綁定的地址,就可以發(fā)起一次調(diào)用。
package com.wdbyte.rmi.client;
import java.rmi.Naming;
import com.wdbyte.rmi.server.User;
import com.wdbyte.rmi.server.UserService;
/**
* @author https://www.wdbyte.com
* @date 2021/05/08
*/
public class RmiClient {
public static void main(String args[]) {
User answer;
String userId = "00001";
try {
// lookup method to find reference of remote object
UserService access = (UserService)Naming.lookup("rmi://localhost:1900/user");
answer = access.findUser(userId);
System.out.println("query:" + userId);
System.out.println("result:" + answer);
} catch (Exception ae) {
System.out.println(ae);
}
}
}
RMI 測(cè)試
啟動(dòng) Server 端。
start server,port is 1900
啟動(dòng) Client 端。
query:00001 result:User{name='金庸', age=100, skill='寫(xiě)作'}
如果 Client 端傳入不存在的 userId。
java.rmi.ServerException: RemoteException occurred in server thread; nested exception is: java.rmi.RemoteException: 查無(wú)此人
serialVersionUID 的生成
IDEA 中生成 serialVersionUID,打開(kāi)設(shè)置,如下圖所示勾選。
選中要生成 serialVersionUID 的類,按智能提示快捷鍵。
參考
[1] https://docs.oracle.com/javase/tutorial/rmi/overview.html
到此這篇關(guān)于詳解Java 中 RMI 的介紹以及使用的文章就介紹到這了,更多相關(guān)java RMI使用內(nèi)容,請(qǐng)搜索W3Cschool以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,也希望大家以后多多支持!