序:本文主要是總結和歸納spring的遠程服務相關知識,可作為入門學習筆記.寫博客目的也是為了進行知識梳理,便於以后查看.本文主要參考資料 spring 實戰第三版
本文主要討論內容如下:
- 遠程調度概
- spring整合遠程方法調用(RMI)服務
- Hessian和Burlap服務與spring的整合
- spring的HTTP invokerspring
- 結合JAX-WS開發Web服務
- Lingo異步通信
- 基於spring消息的遠程調用
- 小結
1.遠程調度概況
遠程調度主要是客戶端與服務端之間的會話.客戶端和服務端的會話開始於客戶端應用的一個遠程過程調用(RPC),即客戶端調用服務器端的方法或函數但又感覺不到是在遠程調用,即程序員認為調用的方法在本地.遠程調度主要是為了提供多個應用或服務之間的信息交流,這種模式適用於SOA(面向服務編程).這里的客戶端不是平常B/S模式下的瀏覽器,而是另一種應用或服務,與服務端所指的應用或服務不在同一個應用或電腦上.
spring支持多種RPC模型,並且對每一種模型都提供了風格一致的支持,意味着只要學習一種模型的spring整合方法變知道其他模型配置方法。這些模型可以分為同步通信和異步通信兩大類。
同步通信:客戶端調用服務端方法時必須等待服務完成才能執行客戶端代碼,必須保證服務端存在。
異步通信:客戶端調用服務端方法時,不需要等待服務的完成便可執行自己的內部方法。
同步通信模型包括:
- 遠程方法調用(RMI):不考慮網絡限制時(如不考慮防火牆),訪問/發布基於java的服務
- Hessian和Blurlap: 考慮網絡限制,通過HTTP訪問/發布基於java的服務
- Sping自帶的HTTP invoker:考慮網絡限制,並希望使用基於XML或專有的java序列化機制,訪問/發布基於spring的服務.(必須要有spring的支持,即服務和spring綁定過緊).
- JAX-WS的Web服務:訪問/發布平台中立的,基於SOAP的Web服務.
異步通信模型:
- Lingo
- 基於spring的消息RPC.
1.1 同步通信和異步通信的區別
同步特點:
- 同步通信意味着客戶端要等待服務端的服務調用完成才能執行客戶端的代碼.對客戶端性能帶來負面影響.
- 客戶端和服務端耦合性高.通過客服端通過服務接口與服務端聯系,如果服務端接口發生變化那么客戶端必須改變;如果服務端不可用,那么客戶端也無法運行;客戶端必須制定服務端的地址.
異步特點:
- 無需等待:客戶端不需要等待服務器的服務完成,提高了客戶端性能.
- 基於JMS的異步通信能夠實現客戶端與服務端的解耦,不需要依賴於特點服務接口.
1.2 spring支持的遠程服務工作原理
在所有的模型中,服務都作為Spring所管理的Bean配置在應用中。這里采用了代理模式的設計模式風格,spring為每個模型都提供了一個代理工廠Bean負責遠程調用,使得能夠像調用本地方法一樣調用遠程服務。具體模式如下:
客戶端向代理發起調用,就像代理提供了這些服務。代理代表客戶端與遠程服務進行通信,並由它負責鏈接細節及向遠處服務發起遠程調用。如果遠程調用時發生java.rmi.RemoteException異常時,代理會處理此異常並重新拋出非檢查型異常RemoteAccessException。
檢查型異常和非檢查型異常的區別:
檢查型異常在Java中所有不是RuntimeException派生的Exception都是檢查型異常。當函數中存在拋出檢查型異常的操作時該函數的函數聲明中必須包含throws語句。
調用改函數的函數也必須對該異常進行處理,如不進行處理則必須在調用函數上聲明throws語句。
非檢查型異常:在Java中所有RuntimeException的派生類都是非檢查型異常,與檢查型異常相對拋出非檢查型異常可以不在函數聲明中添加throws語句,調用函數上也不需要強制處理。
即可以不使用try...catch進行處理,但是如果有異常產生,則異常將由JVM進行處理,也會導致程序中斷。
服務端需要將服務注冊,發布一個遠程服務,才能保證客戶端的代理能夠訪問。服務端的結構特點如下:
2.同步通信模型
2.1spring整合RMI服務
2.1.1 概念:
spring提供了簡單地發布RMI的方式,不用編寫那些需要拋出RemoteException異常的特定RMI類,只需要編寫實現服務功能的POJO.
POJO(Plain Ordinary Java Object),是用來表示普通的Java對象,實質上可以理解為簡單的實體類,但不是JavaBean。POJO不含有業務邏輯,也不實現任何特殊的Java框架的接口如,EJB,JDBC等等。
完全POJO的系統稱為輕量級系統,如Spring.
2.1.2 服務端:發布RMI服務
首先定義一個我們需要發布的服務接口和實現:
服務接口:
public interface UserService{ public void saveUser(User user); public User getUser(int userId); public boolean deleteUser(int userId); }
服務實現(因為在spring的各個模型中都會提供一個遠程輸出器,所以實現類不需要在每個方法中拋出java.rmi.RemoteException異常。這也使得實現了保持純POJO特點。):
public UserServiceImpl implements UserService{ public void saveUser(User user){ System.out.println("saveUser"); } public User getUser(int userId){ System.out.println("getUser"); } public boolean deleteUser(int userId){ System.out.println("getUser"); } }
這個服務接口和服務實現我們將用在下面所有的模型中。接下來就是RMI 發布該服務,提供客戶端調用(這里需要在客戶端放入一份相同的UserService 接口代碼)。發布遠程服務只需要一個遠程輸出器負責將實現類的Bean包裝在一個適配器中,然后適配器被綁定到RMI注冊表中,並將請求代理給服務類。每個模型都是相同的結構,都是通過一個遠程輸出器發布服務,參看圖:
具體代碼:
<bean class="org.springframework.remoting.rmi.RmiServiceExporter" p:service-ref="userService" p:serviceName="userService" p:serviceInterface="rpc.service.UserService" p:registryHost="rmi.user.com" p:registryPort="1199" /> <bean id="userService" class="rpc.service.impl.UserServiceImpl">
service-ref 指定要發布的具體Bean,這里是UserServiceImpl
serviceName:命名該RMI服務
serviceInterface:指定該服務所實現的接口
registryHost: RMI注冊表綁定的主機,默認是本地主機地址
registryPost: RMI注冊表綁定的端口,默認是1099
2.1.3 客戶端
傳統的RMI存在以下問題:
- RMI查找可能會導致3種檢查型異常(RemoteException,NotBoundException,MalformedURLException),所以必須處理異常
- 存在大量的樣版式代碼
所以Spring為RMI和其他遠程調用模塊都提供了一個工廠Bean,負責為服務創建代理。spring為RMI提供RmiProxyFactoryBean工廠,簡化了客戶端操作。
public class UserServiceTest{' @Autowired UserService userService; public void saveUser(User user){ userService.saveUser(user); } }
<bean id="userService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean" p:serviceUrl="rmi://localhost/userService" p:serviceInterface="rmi.service.UserService" />
serviceUrl:指定了服務URL. serviceInterface指定服務實現的接口。 通過RmiProxyFactory不僅可以減少樣板式代碼,還為我們處理異常。同時在調用時根本不知道所用的userService是來自本地還是服務。客戶端和代理的交互圖:
其他模型架構一摸一樣。因為其他模型在sping里使用的模型一樣,下面就直接列出不同模型的代碼。
2.2 Hessian和Burlap服務與spring的整合
2.2.1 Hessian服務端
是基於HTTP協議,所以spring為其提供的遠程輸出類HessianServiceExporter是基於Spring MVC。所以需要在web.xml中配置:
<servlet-mapping> <servlet-name>user</servlet-name> <url-pattern>*.service</url-pattern> </servlet-mapping>
spring配置文件
<!-- url 映射配置 -->
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mapping"> <value>/userService.service=userService</value> </property> </bean>
<!--服務配置 --> <bean id="userService" class="org.springframework.remoting.caucho.HessianServiceExporter" p:service-ref="userService" p:serviceInterface="rpc.service.UserService"/> <bean id="userService" class="rpc.service.imp.UserServiceImpl"/>
所有以userService.service 結尾的URL請求都會由userService的bean處理。 與RMI不同的是這里不需要設置serviceName,因為Hessian沒有注冊表。
2.2.2 Hessian客戶端
配置和RMI基本相同,不同的是serviceUrl標識服務的URL。 對應的代理是HessianProxyFactoryBean.

<bean id="userService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean" p:serviceUrl="http://localhost:8080/user/user.service" p:serviceInterface="rpc.service.UserService"/>
2.2.3 Burlap
Burlap是基於xml協議,它和Hessian一樣,都是基於HTTP協議.Burlap是基於XML的,自然也可以支持很多不同的語言。它需要配置web.xml和spring文件,大部分代碼都一樣,所以這里省略了web.xml和 url的映射配置。

<bean id="userService" class="org.springframework.remoting.caucho.BurlapServiceExporter" p:service-ref="userService" p:serviceInterface="rpc.service.UserService"/> <bean id="userService" class="rpc.service.imp.UserServiceImpl"/>

<bean id="userService" class="org.springframework.remoting.caucho.BurlapProxyFactoryBean" p:serviceUrl="http://localhost:8080/user/user.service" p:serviceInterface="rmi.service.UserService" />
2.3 spring的HTTP invoker
spring 的http invoker是使用http協議(讓防火牆可以接受),並使用了java序列化機制。所以這里在服務端也采用了和hessian,burlap一樣的配置,需要配置web.xml和spring配置文件的url映射。和上面代碼一樣。http invoker有一個嚴重的限制:它是基於spring框架提供的遠程調用解決方案,所以客戶端和服務端必須都是spring應用。

<bean id="userService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter" p:service-ref="userService" p:serviceInterface="rpc.service.UserService"/> <bean id="userService" class="rpc.service.imp.UserServiceImpl"/>

<bean id="userService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean" p:serviceUrl="http://localhost:8080/user/usr.service" p:serviceInterface="rmi.service.UserService" />
2.4 spring 結合JAX-WS開發Web服務
JAX-WS編程模型使用注解將類和方法聲明為Web服務的操作.使用@WebService注解所標注的類被認為Web服務的端點,使用@WebMethod注解所標注的方法被認為是操作.如:
@WebService(serviceName="userService") public UserServiceImpl implements UserService{ @WebMethod public void saveUser(User user){ System.out.println("saveUser"); } @WebMethod public User getUser(int userId){ System.out.println("getUser"); } @WebMethod public boolean deleteUser(int userId){ System.out.println("getUser"); } }
需要在配置文件中配置JAX-WS的導出器,便可以將上面的服務注冊發布:
<bean class="org.springframework.remoting.jaxws.SimpleJaxWsServiceExporter" p:baseAddress="http://localhost:8888/user/" />
默認服務地址為http://localhost:8080/項目名字
3.異步模型
上面介紹的都是同步通信的遠程服務調用.但它存在一些問題如服務端和客戶端的耦合,客戶端等待等問題.下面介紹的是基於JMS(java message service)的遠程調用框架.基於JMS的RPC框架有以下優點:
- 無需等待,客戶端無需等待服務端的服務完成(一般是對無返回值方法的調用采用異步調用)
- 面向消息和解耦:不依賴於服務器和客戶端的服務接口,而是使用JMS發送消息.
- 位置獨立.客戶端不需要知道服務端的地址,便可以發送遠程方法調用.當服務端采用點對點模型時,可以增加服務端數量成立服務集群,這樣多個服務可以從同一個隊列中接收和處理消息,提高服務端的負載.如果采用發布-訂閱模型的話,可以使用不同服務器對相同的服務請求做出不同的實現.
- 如果服務端崩潰不影響客戶端的執行.
3.1 基於spring消息的RPC
服務bean的導出時基於JMS服務.不是完全意義上的異步通信,沒有解決客戶端等待問題,主要是解決客戶端和服務端的耦合問題.Spring 提供的方案有一個缺點就是只能使用點對點消息。
3.1.1 服務端
public interface HelloWord { public void sayHello(String name); } @Component public class HelloWorldImpl implements HelloWorld { @Autowired private Producer producer; public void sayHello(String name) { Mail mail = new Mail(); mail.setContent("Hello," + name); mail.setTo(name); producer.send(mail); } }
JmsInvokerServiceExporter是Spring 提供的基於JMS服務導出的工廠類.
<bean id="jmsServiceExporter" class="org.springframework.jms.remoting.JmsInvokerServiceExporter"> <property name="service" ref="helloService"/> <property name="serviceInterface" value="rpc.service.HelloWorld"/> </bean> <bean id="helloService" class="rpc.service.impl.HelloWorldImpl"/>
將導出器配置為JMS監聽器,這樣它才能夠知道如何連接消息代理.
<amq:connectionFactory id="jmsFactory" /> <jms:listener-container destination-type="queue" connection-factory="jmsFactory" concurrency="3" container-type="simple"> <jms:listener destination="sparta" ref="jmsServiceExporter" /> </jms:listener-container>
3.1.2 客戶端
<amq:connectionFactory id="connectionFactory" /> <bean id="clientService" class="org.springframework.jms.remoting.JmsInvokerProxyFactoryBean" p:serviceInterface="rpc.service.HelloWorld" p:connectionFactory-ref="connectionFactory" p:queueName="sparta"/>
@Test public void testSend() throws JMSException, InterruptedException { myHelloService.sayHello("Test"); }
3.2 Lingo
是基於spring的遠程調用解決方案.它充分利用了JMS來實現在真正的異步服務調用,即客戶端發起請求時,服務端不需要處理可用狀態,客戶端也無需等待服務端的執行完成服務.它的主要思想是,對於無返回的方法調用采用異步通信,有返回數據的方法采用JMS調用.
服務端java代碼與上面的一樣,客戶端java代碼也是.這里主要區別是服務端和客戶端的配置代碼.
<bean id="jmsServiceExporter" class="org.logicblaze.lingo.jms.JmsServiceExporter" p:connectionFactory-ref="connectionFactory" p:destination-ref="sparta" p:service-ref="helloService" p:serviceInterface="rpc.service.HelloWorld" /> <bean id="helloService" class="rpc.service.impl.HelloWorldImpl"/> <amq:queue id="sparta" physicalName="sparta"/>
客戶端:
<bean id="helloService" class="org.logicblaze.lingo.jms.JmsProxyFactoryBean" p:connectionFactory-ref="connectionFactory" p:destination-ref="sparta" P:serviceInterface="rpc.service.HelloWorldService" > <property name="metadataStrategy"> <bean id="metadataStrategy" class="org.logicblaze.lingo.SimpleMetadataStrategy"> <constructor-arg value="true" /> </bean> </property> </bean>
metadataStrategy的構造器參數值設置為true,標識這個所有的void方法都視為單向方法,因此可以被異步調用,無需等待服務端的服務完成便可執行客戶端代碼.
6.小結
本文主要是對spring中的幾個遠程調度模型做一個知識梳理.spring所支持的RPC框架可以分為兩類,同步調用和異步調用.同步調用如:RMI,Hessian,Burlap,Http Invoker,JAX-WS. RMI采用java序列化,但很難穿過防火牆.Hessian,Burlap都是基於http協議,能夠很好的穿過防火牆.但使用了私有的對象序列化機制,Hessian采用二進制傳送數據,而Burlap采用xml,所以Burlap能支持很多語言如python,java等.Http Invoker 是sping基於HTTP和java序列化協議的遠程調用框架,只能用於java程序的通行.Web service(JAX-WS)是連接異構系統或異構語言的首選協議,它使用SOAP形式通訊,可以用於任何語言,目前的許多開發工具對其的支持也很好.
同步通信有一定的局限性.所以出現了異步通信的RPC框架,如lingo和基於sping JMS的RPC框架.