詳解Java 中 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)程訪問。
這個(gè)對(duì)象被稱為遠(yuǎn)程對(duì)象。Server 端需要注冊(cè)這個(gè)對(duì)象可以被 Client 遠(yuǎn)程訪問。
Client 端調(diào)用可以被遠(yuǎn)程訪問的對(duì)象上的方法,Client 端就可以和 Server 端進(jìn)行通信并相互傳遞信息。
說到這里,是不是發(fā)現(xiàn)使用 RMI 在構(gòu)建一個(gè)分布式應(yīng)用時(shí)十分方便,它和 RPC 一樣可以實(shí)現(xiàn)分布式應(yīng)用之間的互相通信,甚至和現(xiàn)在的微服務(wù)思想都十分類似。
RMI 工作原理正所謂 “知其然知其所以然”,在開始編寫 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 開發(fā)通過上面的介紹,知道了 RMI 的概念以及 RMI 的工作原理,下面介紹 RMI 的開發(fā)流程。
這里會(huì)通過一個(gè)場(chǎng)景進(jìn)行演示,假設(shè) Client 端需要查詢用戶信息,而用戶信息存在于 Server 端,所以在 Server 端開放了 RMI 協(xié)議接口供客戶端調(diào)用查詢。
RMI ServerServer 端主要是構(gòu)建一個(gè)可以被傳輸?shù)念?User,一個(gè)可以被遠(yuǎn)程訪問的類 UserService,同時(shí)這個(gè)對(duì)象要注冊(cè)到 RMI 開放給客戶端使用。
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('寫作'); return user;}throw new RemoteException('查無此人'); }}
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)程訪問的對(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)程訪問和需要傳輸?shù)念悾ㄟ^端口和 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:00001result:User{name=’金庸’, age=100, skill=’寫作’}
如果 Client 端傳入不存在的 userId。
java.rmi.ServerException: RemoteException occurred in server thread; nested exception is: java.rmi.RemoteException: 查無此人serialVersionUID 的生成
IDEA 中生成 serialVersionUID,打開設(shè)置,如下圖所示勾選。
選中要生成 serialVersionUID 的類,按智能提示快捷鍵。
參考
[1] https://docs.oracle.com/javase/tutorial/rmi/overview.html
到此這篇關(guān)于詳解Java 中 RMI 的使用的文章就介紹到這了,更多相關(guān)java RMI使用內(nèi)容請(qǐng)搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!
相關(guān)文章:
