一、概念和原理
rpc(remote procedure call),遠程過程調(diào)用,是客戶端應(yīng)用和服務(wù)端之間的會話。在客戶端,它所需要的一些功能并不在該應(yīng)用的實現(xiàn)范圍之內(nèi),所以應(yīng)用要向提供這些功能的其他系統(tǒng)尋求幫助。而遠程應(yīng)用通過遠程服務(wù)暴露這些功能。rpc 是同步操作,會阻塞調(diào)用代碼的執(zhí)行,直到被調(diào)用的過程執(zhí)行完畢。
spring支持多種不同的rpc模型,包括rmi、caucho的hessian和burlap以及spring自帶的http invoker:
客戶端:
在所有的模型中,服務(wù)都是作為 spring 所管理的 bean 配置到我們的應(yīng)用中。這是通過一個代理工廠 bean 實現(xiàn)的,這個bean能夠把遠程服務(wù)像本地對象一樣裝配到其他bean的屬性中。
客戶端向代理發(fā)起調(diào)用,就像代理提供了這些服務(wù)一樣。代理代表客戶端和遠程服務(wù)進行通信,由它負責(zé)處理連接的細節(jié)并向遠程服務(wù)發(fā)起調(diào)用。
服務(wù)端:
spring 使用遠程導(dǎo)出器(remote exporter)將bean方法發(fā)布為遠程服務(wù)。
二、rmi
rmi 最初在jdk 1.1被引入到j(luò)ava平臺中,它為java開發(fā)者提供了一種強大的方法來實現(xiàn)java程序間的交互。
spring 提供了簡單的方式來發(fā)布rmi服務(wù),在服務(wù)端,rmiserviceexporter 可以把任何 spring 管理的bean發(fā)布為rmi服務(wù) ,如圖所示,rmiserviceexporter 把bean包裝在一個適配器類中,然后適配器類被綁定到rmi注冊表中,并且代理到服務(wù)類的請求。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
/** * 服務(wù)端: * <p> * 1、默認情況下,rmiserviceexporter 會嘗試綁定到本地機器1099端口上的rmi注冊表。 * 2、如果在這個端口沒有發(fā)現(xiàn)rmi注冊表,rmiserviceexporter 將會啟動一個注冊表。 * 3、可重寫注冊表的路徑和端口,這個是個大坑,當(dāng)你設(shè)置了registryhost屬性的時候,源碼中就不創(chuàng)建registry,而是直接去獲取,可是我們自己也沒有創(chuàng)建,所以就會報連接不上。 * * @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)發(fā)布為一個rmi服務(wù) rmiexporter.setservicename( "rmiservice" ); //命名rmi 服務(wù) rmiexporter.setserviceinterface(userservice. class ); //指定服務(wù)所實現(xiàn)的接口 rmiexporter.setregistryhost(registryhost); rmiexporter.setregistryport(registryport); return rmiexporter; } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
/** * created by xiuyin.cui on 2018/5/14. * * 解決設(shè)置 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起來,報異常就創(chuàng)建一個 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); } } } |
接下來,來看看客戶端是怎么使用這些遠程服務(wù)的吧!spring的rmiproxyfactorybean是一個工廠bean,該bean可以為rmi服務(wù)創(chuàng)建代理。該代理代表客戶端來負責(zé)與遠程的rmi服務(wù)進行通信。客戶端通過服務(wù)的接口與代理進行交互,就如同遠程服務(wù)就是一個本地的pojo。
1
2
3
4
5
6
7
8
9
10
|
@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 ); //不在容器啟動后創(chuàng)建與server端的連接 rmiproxyfactorybean.setrefreshstubonconnectfailure( true ); //連接出錯的時候自動重連 rmiproxyfactorybean.afterpropertiesset(); return rmiproxyfactorybean; } |
1
2
|
@resource (name= "rmiuserserviceclient" ) private userservice userservice; |
rmi 的缺陷:
1、rmi很難穿越防火墻,這是因為rmi使用任意端口來交互——這是防火墻通常所不允許的。
2、rmi是基于java的。這意味著客戶端和服務(wù)端必須都是用java開發(fā)。因為rmi使用了java的序列化機制,所以通過網(wǎng)絡(luò)傳輸?shù)膶ο箢愋捅仨氁WC在調(diào)用兩端的java運行時中是完全相同的版本。
tips:最近發(fā)現(xiàn)dubbo 底層也是用 rmi 實現(xiàn)的,它把 zookeeper 當(dāng)作注冊表。
三、hessian 和 burlap
hessian 和 burlap 是 caucho technology 的兩種基于http的輕量級遠程服務(wù)解決方案。借助于盡可能簡單的api和通信協(xié)議,它們都致力于簡化web服務(wù)。
hessian,像rmi一樣,使用二進制消息進行客戶端和服務(wù)端的交互。但是它與rmi不同的是,它的二進制消息可以移植到其他非java的語言中。由于它是基于二進制的,所以它在帶寬上更具優(yōu)勢。
burlap 是一種基于xml的遠程調(diào)用技術(shù),這使得它可以自然而然的移植到任何能夠解析xml的語言上。正因為它基于xml,所以相比起hessian的二進制格式而言,burlap可讀性更強。但是和其他基于xml的遠程技術(shù)(例如soap或xml-rpc)不同,burlap的消息結(jié)構(gòu)盡可能的簡單。
下面我們會介紹 hessian 的使用。spring 不推薦使用burlap,burlapserviceexporter 在4.0后被廢棄,不再提供支持。5.0 后直接從開發(fā)包丟棄了。
服務(wù)端,類似于 rmiserviceexporter ,hessian 也有一個hessianserviceexporter 將 spring 管理的 bean 發(fā)布為 hessian 服務(wù),不同于rmi的是,hessianserviceexporter是一個spring mvc控制器,它接收hessian請求(http協(xié)議的請求),并將這些請求轉(zhuǎn)換成對被導(dǎo)出pojo的方法調(diào)用。既然是http請求,那我們就必須配置spring 的dispatcherservlet ,并配置handlermapping,將相應(yīng)的url映射給hessianserviceexporter。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
/** * hessian沒有注冊表,不需要設(shè)置 servicename */ @bean (name = "hessianserviceexporter" ) public hessianserviceexporter hessianserviceexporter(userservice userservice) { hessianserviceexporter hessianserviceexporter = new hessianserviceexporter(); hessianserviceexporter.setservice(userservice); hessianserviceexporter.setserviceinterface(userservice. class ); return hessianserviceexporter; } /** * 需要配置一個url映射來確保dispatcherservlet把請求轉(zhuǎn)給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,來創(chuàng)建代理與遠程服務(wù)進行通信:
1
2
3
4
5
6
7
|
@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; } |
1
2
|
@resource (name= "hessianuserserviceclient" ) private userservice userservice; |
hessian 的缺陷:
hessian 和 burlap 都是基于http的,它們都解決了rmi所頭疼的防火墻滲透問題。但是當(dāng)傳遞過來的rpc消息中包含序列化對象時,rmi就完勝 hessian 和 burlap 了。因為 hessian 和 burlap 都采用了私有的序列化機制,而rmi使用的是java本身的序列化機制。
四、httpinvoker
rmi 和 hessian 各有自己的缺陷,一方面,rmi使用java標準的對象序列化機制,但是很難穿透防火墻。另一方面,hessian和burlap能很好地穿透防火墻,但是使用私有的對象序列化機制。就這樣,spring的http invoker應(yīng)運而生了。http invoker是一個新的遠程調(diào)用模型,作為spring框架的一部分,能夠執(zhí)行基于http的遠程調(diào)用,并使用java的序列化機制。
httpinvoker 的使用和 hessian 很類似,httpinvokerserviceexporter 也是一個spring mvc 控制器,也是通過dispatcherservlet 將請求分發(fā)給它...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/*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把請求轉(zhuǎn)給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,用于創(chuàng)建httpinvoker代理來與遠程服務(wù)通信:
1
2
3
4
5
6
7
|
@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; } |
參考資料:《spring 實戰(zhàn)第四版》
演示源代碼鏈接:https://github.com/jmcuixy/springforrpc
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。
原文鏈接:https://www.cnblogs.com/jmcui/p/9044212.html