一、概念和原理
RPC(remote procedure call),遠端過程調用,是用戶端應用和服務端之間的會話。在用戶端,它所需要的一些功能并不在該應用的實作範圍之内,是以應用要向提供這些功能的其他系統尋求幫助。而遠端應用通過遠端服務暴露這些功能。RPC 是同步操作,會阻塞調用代碼的執行,直到被調用的過程執行完畢。
Spring支援多種不同的RPC模型,包括RMI、Caucho的Hessian和Burlap以及Spring自帶的HTTP invoker:

用戶端:
在所有的模型中,服務都是作為 Spring 所管理的 bean 配置到我們的應用中。這是通過一個代理工廠 bean 實作的,這個bean能夠把遠端服務像本地對象一樣裝配到其他bean的屬性中。
用戶端向代理發起調用,就像代理提供了這些服務一樣。代理代表用戶端和遠端服務進行通信,由它負責處理連接配接的細節并向遠端服務發起調用。
服務端:
Spring 使用遠端導出器(remote exporter)将bean方法釋出為遠端服務。
二、RMI
RMI 最初在JDK 1.1被引入到Java平台中,它為Java開發者提供了一種強大的方法來實作Java程式間的互動。
Spring 提供了簡單的方式來釋出RMI服務,在服務端,RmiServiceExporter 可以把任何 Spring 管理的bean釋出為RMI服務 ,如圖所示,RmiServiceExporter 把bean包裝在一個擴充卡類中,然後擴充卡類被綁定到RMI系統資料庫中,并且代理到服務類的請求。
/**
* 服務端:
* <p>
* 1、預設情況下,RmiServiceExporter 會嘗試綁定到本地機器1099端口上的RMI系統資料庫。
* 2、如果在這個端口沒有發現RMI系統資料庫,RmiServiceExporter 将會啟動一個系統資料庫。
* 3、可重寫系統資料庫的路徑和端口,這個是個大坑,當你設定了registryHost屬性的時候,源碼中就不建立Registry,而是直接去擷取,可是我們自己也沒有建立,是以就會報連接配接不上。
*
* @param userService
* @return
*/
@Bean(name = "rmiServiceExporter")
public RmiExporter rmiServiceExporter(UserService userService, Environment environment) {
String registryHost = environment.getProperty("registryHost");
int registryPort = environment.getProperty("registryPort", Integer.class);
RmiExporter rmiExporter = new RmiExporter();
rmiExporter.setService(userService); //要把該bean(即rmiServiceImpl)釋出為一個RMI服務
rmiExporter.setServiceName("RmiService"); //命名RMI 服務
rmiExporter.setServiceInterface(UserService.class); //指定服務所實作的接口
rmiExporter.setRegistryHost(registryHost);
rmiExporter.setRegistryPort(registryPort);
return rmiExporter;
}
/**
* Created by XiuYin.Cui on 2018/5/14.
*
* 解決設定 registryHost 後,報連接配接拒絕的問題。
*/
public class RmiExporter extends RmiServiceExporter {
@Override
protected Registry getRegistry(String registryHost, int registryPort, RMIClientSocketFactory clientSocketFactory,
RMIServerSocketFactory serverSocketFactory) throws RemoteException {
if (registryHost != null) {
try {
if (logger.isInfoEnabled()) {
logger.info("Looking for RMI registry at port '" + registryPort + "' of host [" + registryHost + "]");
}
//把spring源代碼中這裡try起來,報異常就建立一個
Registry reg = LocateRegistry.getRegistry(registryHost, registryPort, clientSocketFactory);
testRegistry(reg);
return reg;
} catch (RemoteException ex) {
LocateRegistry.createRegistry(registryPort);
Registry reg = LocateRegistry.getRegistry(registryHost, registryPort, clientSocketFactory);
testRegistry(reg);
return reg;
}
} else {
return getRegistry(registryPort, clientSocketFactory, serverSocketFactory);
}
}
}
View Code
接下來,來看看用戶端是怎麼使用這些遠端服務的吧!Spring的RmiProxyFactoryBean是一個工廠bean,該bean可以為RMI服務建立代理。該代理代表用戶端來負責與遠端的RMI服務進行通信。用戶端通過服務的接口與代理進行互動,就如同遠端服務就是一個本地的POJO。
@Bean(name = "rmiUserServiceClient")
public RmiProxyFactoryBean RmiUserServiceClient(){
RmiProxyFactoryBean rmiProxyFactoryBean = new RmiProxyFactoryBean();
rmiProxyFactoryBean.setServiceUrl("rmi://127.0.0.1:9999/RmiService");
rmiProxyFactoryBean.setServiceInterface(UserService.class);
rmiProxyFactoryBean.setLookupStubOnStartup(false);//不在容器啟動後建立與Server端的連接配接
rmiProxyFactoryBean.setRefreshStubOnConnectFailure(true);//連接配接出錯的時候自動重連
rmiProxyFactoryBean.afterPropertiesSet();
return rmiProxyFactoryBean;
}
@Resource(name="rmiUserServiceClient")
private UserService userService;
RMI 的缺陷:
1、RMI很難穿越防火牆,這是因為RMI使用任意端口來互動——這是防火牆通常所不允許的。
2、RMI是基于Java的。這意味着用戶端和服務端必須都是用java開發。因為RMI使用了Java的序列化機制,是以通過網絡傳輸的對象類型必須要保證在調用兩端的Java運作時中是完全相同的版本。
tips:最近發現 Dubbo 底層也是用 RMI 實作的,它把 zookeeper 當作系統資料庫。
三、Hessian 和 Burlap
hessian 和 Burlap 是 Caucho Technology 的兩種基于HTTP的輕量級遠端服務解決方案。借助于盡可能簡單的API和通信協定,它們都緻力于簡化Web服務。
hessian,像RMI一樣,使用二進制消息進行用戶端和服務端的互動。但是它與RMI不同的是,它的二進制消息可以移植到其他非Java的語言中。由于它是基于二進制的,是以它在帶寬上更具優勢。
Burlap 是一種基于XML的遠端調用技術,這使得它可以自然而然的移植到任何能夠解析XML的語言上。正因為它基于XML,是以相比起Hessian的二進制格式而言,Burlap可讀性更強。但是和其他基于XML的遠端技術(例如SOAP或XML-RPC)不同,Burlap的消息結構盡可能的簡單。
下面我們會介紹 hessian 的使用。Spring 不推薦使用 Burlap,BurlapServiceExporter 在4.0後被廢棄,不再提供支援。5.0 後直接從開發包丢棄了。
服務端,類似于 RmiServiceExporter ,hessian 也有一個 HessianServiceExporter 将 Spring 管理的 bean 釋出為 Hessian 服務,不同于RMI的是,HessianServiceExporter是一個Spring MVC控制器,它接收Hessian請求(HTTP協定的請求),并将這些請求轉換成對被導出POJO的方法調用。既然是HTTP請求,那我們就必須配置Spring 的 DispatcherServlet ,并配置 HandlerMapping,将相應的URL映射給 HessianServiceExporter。
/**
* hessian沒有系統資料庫,不需要設定 serviceName
*/
@Bean(name = "hessianServiceExporter")
public HessianServiceExporter hessianServiceExporter(UserService userService) {
HessianServiceExporter hessianServiceExporter = new HessianServiceExporter();
hessianServiceExporter.setService(userService);
hessianServiceExporter.setServiceInterface(UserService.class);
return hessianServiceExporter;
}
/**
* 需要配置一個URL映射來確定DispatcherServlet把請求轉給HessianServiceExporter
*/
@Bean(name = "handlerMapping")
public HandlerMapping handlerMapping() {
SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
Properties mappings = new Properties();
mappings.setProperty("/user.service", "hessianServiceExporter");
handlerMapping.setMappings(mappings);
return handlerMapping;
}
用戶端,類似于 RmiProxyFactoryBean ,Hessian 也有一個代理工廠Bean——HessianProxyFactoryBean,來建立代理與遠端服務進行通信:
@Bean(name = "hessianUserServiceClient")
public HessianProxyFactoryBean hessianUserServiceClient(){
HessianProxyFactoryBean proxy = new HessianProxyFactoryBean();
proxy.setServiceUrl("http://127.0.0.1:8080/user.service");
proxy.setServiceInterface(UserService.class);
return proxy;
}
@Resource(name="hessianUserServiceClient")
private UserService userService;
Hessian 的缺陷:
hessian 和 Burlap 都是基于HTTP的,它們都解決了RMI所頭疼的防火牆滲透問題。但是當傳遞過來的RPC消息中包含序列化對象時,RMI就完勝 Hessian 和 Burlap 了。因為 Hessian 和 Burlap 都采用了私有的序列化機制,而RMI使用的是Java本身的序列化機制。
四、HttpInvoker
RMI 和 Hessian 各有自己的缺陷,一方面,RMI使用Java标準的對象序列化機制,但是很難穿透防火牆。另一方面,Hessian和Burlap能很好地穿透防火牆,但是使用私有的對象序列化機制。就這樣,Spring的HTTP invoker應運而生了。HTTP invoker是一個新的遠端調用模型,作為Spring架構的一部分,能夠執行基于HTTP的遠端調用,并使用Java的序列化機制。
HttpInvoker 的使用和 Hessian 很類似,HttpInvokerServiceExporter 也是一個Spring MVC 控制器,也是通過 DispatcherServlet 将請求分發給它...
/*Http Invoker*/
@Bean(name = "httpInvokerServiceExporter")
public HttpInvokerServiceExporter httpInvokerServiceExporter(UserService userService){
HttpInvokerServiceExporter httpInvokerServiceExporter = new HttpInvokerServiceExporter();
httpInvokerServiceExporter.setService(userService);
httpInvokerServiceExporter.setServiceInterface(UserService.class);
return httpInvokerServiceExporter;
}
/**
* 需要配置一個URL映射來確定DispatcherServlet把請求轉給HessianServiceExporter
*/
@Bean(name = "handlerMapping")
public HandlerMapping handlerMapping() {
SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
Properties mappings = new Properties();
mappings.setProperty("/user.service", "hessianServiceExporter");
mappings.setProperty("/userInvoker.service", "httpInvokerServiceExporter");
handlerMapping.setMappings(mappings);
return handlerMapping;
}
用戶端,像 RmiProxyFactoryBean 和 HessianProxyFactoryBean 一樣,HttpInvoker 也提供了一個代理工廠Bean——HttpInvokerProxyFactoryBean,用于建立HttpInvoker代理來與遠端服務通信:
@Bean(name = "httpInvokerUserServiceClient")
public HttpInvokerProxyFactoryBean httpInvokerUserServiceClient(){
HttpInvokerProxyFactoryBean proxy = new HttpInvokerProxyFactoryBean();
proxy.setServiceUrl("http://127.0.0.1:8080//userInvoker.service");
proxy.setServiceInterface(UserService.class);
return proxy;
}
@Resource(name="httpInvokerUserServiceClient")
private UserService userService;
參考資料:《Spring 實戰第四版》
示範源代碼連結:
https://github.com/JMCuixy/SpringForRpc