1 、RMI是什麼
在 Java 世界裏,有一種技術可以實現“跨虛擬機”的調用,它就是 RMI(Remote Method Invocation,遠程方法調用)。例如,服務A 在 JVM1 中運行,服務B 在 JVM2 中運行,服務A 與 服務B 可相互進行遠程調用,就像調用本地方法一樣,這就是 RMI。在分佈式系統中,我們使用 RMI 技術可輕鬆將服務提供者(Service Provider)與服務消費者(Service Consumer)進行分離,充分體現組件之間的弱耦合,系統架構更易於擴展。
2、RMI遠程方法調用原理
方法調用從客戶對象經佔位程序(Stub)、遠程引用層(Remote Reference Layer)和傳輸層(Transport Layer)向下,傳遞給主機,然後再次經傳 輸層,向上穿過遠程調用層和骨幹網(Skeleton),到達服務器對象。 佔位程序扮演着遠程服務器對象的代理的角色,使該對象可被客戶激活。 遠程引用層處理語義、管理單一或多重對象的通信,決定調用是應發往一個服務器還是多個。傳輸層管理實際的連接,並且追蹤可以接受方法調用的遠程對象。服務器端的骨幹網完成對服務器對象實際的方法調用,並獲取返回值。返回值向下經遠程引用層、服務器端的傳輸層傳遞迴客戶端,再向上經傳輸層和遠程調用層返回。最後,佔位程序獲得返回值。
要完成以上步驟需要有以下幾個步驟:
(1)、 生成一個遠程接口
(2)、 實現遠程對象(服務器端程序)
(3)、 生成佔位程序和骨幹網(服務器端程序)
(4)、 編寫服務器程序
(5)、 編寫客戶程序
(6)、 註冊遠程對象
(7)、 啓動遠程對象
3、發佈RMI服務
發佈的一個RMI服務,我們需做三件事情
1、定義一個RMI接口
2、編寫RMI接口的實現類
3、通過JNDI發佈RMI服務
3.1 定義一個RMI接口
RMI接口實際上還是一個普通的Java接口,只是RMI接口必須繼承java.rmi.Remote,此外,RMI接口內的每個方法必須聲明拋出一個java.rmi.RemoteException異常,就像下面這樣
package org.bird.rmi2;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface CalculateService extends Remote {
public int add(int a, int b) throws RemoteException;
}
繼承了Remote接口,實際上就是讓JVM知道該接口需要用於遠程調用的,拋出RemoteException是爲了讓調用RMI服務的程序捕獲這個異常根據相應異常情況進行後續處理,畢竟遠程調用過程中,什麼奇怪的事情都有可能發生(如:斷網)。需要說明的是,RemoteException是一個”受檢異常“,在調用的時候必須使用try…catch…自行處理。
3.2 編寫RMI接口的實現類
實現上面的CalculateService比較簡單,但是需要注意的是,我們必須讓實現類繼承java.rmi.server.UnicastRemoteObject類,此外,必須提供一個構造器,並且構造器必須拋出java.rmi.RemoteException異常。所以說,我們使用JVM提供的這一套RMI框架,就必須按照這些要求來實現,否則是無法成功發佈RMI服務的。就像下面這樣
package org.bird.rmi2;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class CalculateServiceImpl extends UnicastRemoteObject implements
CalculateService {
/**
*
*/
private static final long serialVersionUID = 1L;
protected CalculateServiceImpl() throws RemoteException {
super();
}
public int add(int a, int b) throws RemoteException {
return a + b;
}
}
爲了滿足RMI框架的要求,我們確實需要做很多額外的工作(繼承了UnicastRemoteObject類,拋出RemoteException異常)。我們可以通過JVM提供的JNDI(Java Naming and Directory Interface,Java 命名與目錄接口)這個API方便的發佈RMI服務。
3.3通過JNDI發佈RMI服務
發佈RMI服務,我們需要告訴JNDI三個基本信息:1.域名或IP地址(host)、2.端口號(port)、3、服務名(service),它們構成了RMI協議的URL
rmi://<host>:<port>/<service>
如果我們是在本地發佈RMI服務,那麼host就是localhost。此外RMI默認的端口是1099,我們也可以自行設置port的值(可以與其他端口衝突)。service是基於host與port下唯一的服務名稱。需要保證RMI地址的唯一性。對於我們示例,RMI地址爲:
rmi://localhost:1099/calculate
我們提供一個main()方法來發布示例RMI服務,就像下面
package org.bird.rmi2;
import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
public class RMIServer {
/**
* @param args
* @throws RemoteException
* @throws AlreadyBoundException
* @throws MalformedURLException
*/
public static void main(String[] args) throws RemoteException, MalformedURLException, AlreadyBoundException {
int port = 1099;
LocateRegistry.createRegistry(port);
String url = "rmi://localhost:" + port + "/calculate";
Naming.bind(url, new CalculateServiceImpl());
}
}
我們通過LocateRegistry.createRegistry()方法在JNDI中創建一個註冊表,只需提供一個RMI端口即可。通過Naming.bind()方法綁定RMI地址與服務的實現類。運行這個main()方法,RMI服務就會自動發佈,剩下的事情就是寫一個RMI客戶端來調用剛發佈的RMI服務。
4、調用RMI服務
同樣,我們也利用一個main()方法來調用RMI服務。調用服務我們只需要知道兩個信息:1.RMI請求地址、2.RMI接口(一定不要使用RMI的實現類,否則就是本地調用)。少量代碼就能調用剛纔發佈的RMI服務了,就像下面這樣
package org.bird.rmi2;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
public class RMIClient {
/**
* @param args
* @throws NotBoundException
* @throws RemoteException
* @throws MalformedURLException
*/
public static void main(String[] args) throws MalformedURLException, RemoteException, NotBoundException {
String url = "rmi://localhost:1099/calculate";
CalculateService calculate = (CalculateService) Naming.lookup(url);
int sum = calculate.add(100, 400);
System.out.println(sum);
}
}
當我們運行以上的main()方法,在控制檯看到”500“輸出,表明RMI調用成功。
本文轉自:https://www.iteye.com/blog/liangjf85-163-com-2162928
Java實現簡易RPC框架參考一:Java實現簡易RPC框架(一)
二:Java實現簡易RPC框架(二)
三:Java實現簡易RPC框架(三)