今天我們來介紹一下JAVA RMI,大家或許之前從來沒聽說過這個詞蚀浆,但是如果你用過dubbo陈哑、spring cloud等分布式服務(wù)治理框架,理解起來RMI是很輕松的盲赊,因?yàn)樗鼈兊母拍钍遣畈欢嗟南撑簦徊贿^使用的底層協(xié)議不同。
一般情況下哀蘑,在我們本地一個類在調(diào)用另一個類的方法時(shí)诚卸,調(diào)用方與被調(diào)用方都是在同一個堆上的。
如果要從服務(wù)器A調(diào)用服務(wù)器B的方法是辦不到的递礼,因?yàn)樗鼈冊趦蓚€不同的堆上惨险,JVM虛擬機(jī)只能控制自己堆上面的引用地址應(yīng)該如何傳遞的羹幸。
如果想調(diào)用不同的服務(wù)器上面的方法脊髓,要怎么辦?如何設(shè)計(jì)栅受?
下面我們來介紹一下将硝,JAVA RMI是個啥,它是如何實(shí)現(xiàn)不同堆上的調(diào)用屏镊?
JAVA RMI(Remote Method Invocation):大致字面的意思就是執(zhí)行遠(yuǎn)程服務(wù)器方法依疼。
想要利用RMI實(shí)現(xiàn)遠(yuǎn)程調(diào)用,需要創(chuàng)建四個關(guān)鍵對象:
客服端而芥、客戶端輔助對象律罢、服務(wù)端輔助對象、服務(wù)端。
.客戶端:調(diào)用遠(yuǎn)程服務(wù)對象误辑。
.服務(wù)端:帶有客戶端會調(diào)用的方法的對象沧踏。
.客戶端輔助對象(stub):處理客戶端的低層網(wǎng)絡(luò)輸出字節(jié),連接遠(yuǎn)程服務(wù)端巾钉。
.服務(wù)端輔助對象(seleton):處理服務(wù)端的低層網(wǎng)絡(luò)輸入字節(jié)翘狱,連接遠(yuǎn)程客戶端。
輔助對象實(shí)際上就是通信對象砰苍,沒有任何的方法邏輯潦匈。
先簡單描述一下調(diào)用方法的過程:
1.客戶端對象對客戶端輔助對象(stub)調(diào)用dothing()
2.客戶端輔助對象把調(diào)用信息打包(序列化)通過網(wǎng)絡(luò)送到服務(wù)器的服務(wù)端打輔助對象。
3.服務(wù)端輔助對象反序列化得到客戶端輔助對象的信息赚导,并以此調(diào)用真正的服務(wù)茬缩。
如何利用代碼創(chuàng)建遠(yuǎn)程服務(wù)
1.創(chuàng)建Remote接口:
import java.rmi.Remote;
import java.rmi.RemoteException;
/**
* @Author:zhangyanan
* @Description: 定義遠(yuǎn)程服務(wù)接口
* @Date:Crated in 19:22 2020/3/11
* @Modified By:
*/
public interface MyRemote extends Remote {
/**
* 每個遠(yuǎn)程調(diào)用都被認(rèn)為是有風(fēng)險(xiǎn)的,這樣聲明會強(qiáng)迫客戶端注意到這件事
*
* @return
* @throws RemoteException
*/
String sayHello() throws RemoteException;
}
上面定義接口需要注意的是:
(1)任何遠(yuǎn)程方法的參數(shù)都會被打包通過網(wǎng)絡(luò)傳送,而這是需要序列化完成的辟癌。
(2)如果返回的是自定義類型需要自定義類實(shí)現(xiàn)serialzable接口寒屯。
2.實(shí)現(xiàn)Remote遠(yuǎn)程服務(wù)接口:
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
/**
* @Author:zhangyanan
* @Description: 遠(yuǎn)程服務(wù)端真實(shí)的服務(wù)實(shí)現(xiàn)
* @Date:Crated in 19:26 2020/3/11
* @Modified By:
*/
public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote {
protected MyRemoteImpl() throws RemoteException {
}
@Override
public String sayHello() throws RemoteException {
return null;
}
//下面的main函數(shù)是為了向注冊中心注冊服務(wù),以便客戶端可以查找到遠(yuǎn)程服務(wù)并調(diào)用
public static void main(String[] args) {
try {
MyRemote service = new MyRemoteImpl();
Naming.rebind("Remote Hello",service);
} catch (RemoteException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
(1)實(shí)現(xiàn)MyRemote,就是給客戶端要調(diào)用的方法黍少。
(2)繼承UnicastRemoteObject類寡夹,為了要成為遠(yuǎn)程服務(wù)對象,你的類必須要有與遠(yuǎn)程有關(guān)的功能厂置。其中最簡單的方式就是繼承UnicastRemoteObject菩掏,讓它來處理遠(yuǎn)程連接這些工作。
(3)編寫聲明RemoteException無參構(gòu)造函數(shù):因?yàn)楦割怳nicastRemoteObject構(gòu)造方法定義了異常昵济,所以當(dāng)類初始化的時(shí)候一定會被調(diào)用智绸,如果父類拋出異常,那么子類也要聲明構(gòu)造函數(shù)可能會拋出異常访忿。
3.利用JDK工具rmic產(chǎn)生stub和skeleton:
打開命令窗口瞧栗,對遠(yuǎn)程服務(wù)實(shí)現(xiàn)類執(zhí)行rmic:
rmic MyRemoteImpl
此時(shí)會生成MyRemoteImpl_Stub.class和MyRemoteImpl_skel.class兩個輔助類class,它們是用于連接客戶端和服務(wù)端的海铆。
4.啟動RMI registry(注冊服務(wù)):
打開命令窗口迹恐,執(zhí)行:
rmiregistry
意思是:打開了一個注冊中心,類似于電話號碼本卧斟,用于記錄服務(wù)的地址殴边,方便客戶端查找服務(wù)。
5.啟動遠(yuǎn)程服務(wù):
java MyRemoteImpl
此操作會執(zhí)行MyRemoteImpl類中的mian函數(shù)珍语,使用rebind方法向注冊中心注冊服務(wù)锤岸。
以上就是服務(wù)端要做的事情。
下面我們來說一下客戶端該如何調(diào)用服務(wù):
public class Test {
public static void main(String[] args) {
try {
MyRemote myRemote = (MyRemote) Naming.lookup("rmi://127.0.0.1/Remote Hello");
} catch (NotBoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
1.客戶端查詢服務(wù):
利用Naming.lookup()方法去registry(注冊中心)尋找要調(diào)用的遠(yuǎn)程服務(wù)板乙,客戶端就像搜電話本一樣是偷,找出上面名稱相符的名稱的服務(wù)。
2.RMI registry返回stub對象,它不是真實(shí)的服務(wù)蛋铆,是真實(shí)服務(wù)的代理饿幅,此對象沒有任何的方法邏輯。它會將調(diào)用的信息傳遞過去(像是方法的名稱和參數(shù)內(nèi)容)戒职,等待服務(wù)器響應(yīng)然后再返回到客戶端栗恩。
3.客戶端就像取用真正的服務(wù)一樣的調(diào)用stub上的方法。
需要注意的是:
(1)客戶端在查詢服務(wù)的時(shí)候必須要有stub類(MyRemoteImpl_Stub.class)洪燥,不然無法在客戶端反序列化磕秤。
(2)服務(wù)端也必須同時(shí)要有stub類和seleton類,它會需要stub類是因?yàn)閟tub會被代換成連接在RMIregistry上真正的服務(wù)捧韵。