http://ajava.org/online/spring2.5/html/remoting.html#remoting-rmi
簡(jiǎn)介
Spring 為各種遠(yuǎn)程訪問(wèn)技術(shù)的集成提供了整合類。Spring使得開(kāi)發(fā)具有遠(yuǎn)程訪問(wèn)功能的服務(wù)變得相當(dāng)容易,而這些遠(yuǎn)程訪問(wèn)服務(wù)由普通Spring POJO實(shí)現(xiàn)。目前,Spring支持四種遠(yuǎn)程技術(shù):
-
遠(yuǎn)程方法調(diào)用(RMI) 。通過(guò)使用 RmiProxyFactoryBean 和 RmiServiceExporter ,Spring同時(shí)支持傳統(tǒng)的RMI(使用 java.rmi.Remote 接口和 java.rmi.RemoteException ) 和通過(guò)RMI調(diào)用器實(shí)現(xiàn)的透明遠(yuǎn)程調(diào)用(支持任何Java接口)。
-
Spring的HTTP調(diào)用器 。Spring提供了一種允 許通過(guò)HTTP進(jìn)行Java串行化的特殊遠(yuǎn)程調(diào)用策略,它支持任意Java接口(就像RMI調(diào)用器)。相對(duì)應(yīng)的支持類是 HttpInvokerProxyFactoryBean 和 HttpInvokerServiceExporter 。
-
Hessian 。通過(guò) HessianProxyFactoryBean 和 HessianServiceExporter , 可以使用Caucho提供的基于HTTP的輕量級(jí)二進(jìn)制協(xié)議來(lái)透明地暴露服務(wù)。
-
Burlap 。 Burlap是Caucho基于XML用來(lái)替代Hessian的項(xiàng)目。Spring提供了諸如 BurlapProxyFactoryBean 和 BurlapServiceExporter 的支持類。
-
JAX RPC 。Spring通過(guò)JAX- RPC(J2EE 1.4's wweb service API)為Web services提供遠(yuǎn)程服務(wù)支持。
-
JAX-WS . Spring通過(guò)(在Java EE 5和Java 6中引入的JAX-RPC繼承)為遠(yuǎn)程Web Services提供支持。
-
JMS . 通過(guò) JmsInvokerServiceExporter 和 JmsInvokerProxyFactoryBean 使用JMS做為底層協(xié)議提供遠(yuǎn)程服務(wù).
在 討論Spring對(duì)遠(yuǎn)程訪問(wèn)的支持時(shí),我們將使用下面的域模型和對(duì)應(yīng)的服務(wù):
public class Account implements Serializable{
private String name;
public String getName();
public void setName(String name) {
this.name = name;
}
}
public interface AccountService {
public void insertAccount(Account account);
public List getAccounts(String name);
}
public interface RemoteAccountService extends Remote {
public void insertAccount(Account account) throws RemoteException;
public List getAccounts(String name) throws RemoteException;
}
// 該實(shí)現(xiàn)目前什么事情也不做
public class AccountServiceImpl implements AccountService {
public void insertAccount(Account acc) {
// 做一些事情……
}
public List getAccounts(String name) {
// 做一些事情……
}
}
我們將從使用RMI把服務(wù)暴露給遠(yuǎn)程客戶端開(kāi)始,同時(shí)探討RMI的一些缺點(diǎn)。然后我們將演示一個(gè)使用Hessian的例子。
使用Spring的RMI支持,你可以通過(guò)RMI基礎(chǔ)設(shè)施透 明的暴露你的服務(wù)。設(shè)置好Spring的RMI支持后,你會(huì)看到一個(gè)和遠(yuǎn)程EJB接口類似的配置,只是沒(méi)有對(duì)安全上下文傳遞和遠(yuǎn)程事務(wù)傳遞的標(biāo)準(zhǔn)支持。當(dāng) 使用RMI調(diào)用器時(shí),Spring對(duì)這些額外的調(diào)用上下文提供了鉤子,你可以在此插入安全框架或者定制的安全證書(shū)。
使 用 RmiServiceExporter ,我們可以把AccountService對(duì)象的接口暴 露成RMI對(duì)象。可以使用 RmiProxyFactoryBean 或者在傳統(tǒng)RMI服務(wù)中使用普通RMI來(lái)訪問(wèn)該接口。 RmiServiceExporter 顯式地支持使用RMI調(diào)用器暴露任何非RMI的服務(wù)。
當(dāng)然,我們首先需要在Spring容器中設(shè)置我們的服務(wù):
<bean id="accountService" class="example.AccountServiceImpl">
<!--其他屬性,或者一個(gè)DAO對(duì)象?-->
</bean>
然后我們要使用 RmiServiceExporter 來(lái) 暴露我們的服務(wù):
<bean class="org.springframework.remoting.rmi.RmiServiceExporter">
<!-- 不一定要與要輸出的bean同名-->
<property name="serviceName" value="AccountService"/>
<property name="service" ref="accountService"/>
<property name="serviceInterface" value="example.AccountService"/>
<!-- 默認(rèn)為1199-->
<property name="registryPort" value="1199"/>
</bean>
正如你所見(jiàn),我們覆蓋了RMI注冊(cè)的端口號(hào)。通常你的應(yīng)用服務(wù)器也會(huì)維護(hù)RMI注冊(cè),最好不要和它沖突。更 進(jìn)一步來(lái)說(shuō),服務(wù)名是用來(lái)綁定服務(wù)的。所以本例中,服務(wù)綁定在 rmi://HOST:1199/AccountService 。 在客戶端我們將使用這個(gè)URL來(lái)鏈接到服務(wù)。
servicePort 屬性被省略了(它的默認(rèn)值為0).這表示在與服務(wù)通信時(shí)將使用匿名端口. |
我 們的客戶端是一個(gè)使用 AccountService 來(lái)管理account的簡(jiǎn)單對(duì)象:
public class SimpleObject {
private AccountService accountService;
public void setAccountService(AccountService accountService) {
this.accountService = accountService;
}
}
為了把服務(wù)連接到客戶端上,我們將創(chuàng)建一個(gè)單獨(dú)的Spring容器,包含這個(gè)簡(jiǎn)單對(duì)象和鏈接配置位的服務(wù):
<bean class="example.SimpleObject">
<property name="accountService" ref="accountService"/>
</bean>
<bean id="accountService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl" value="rmi://HOST:1199/AccountService"/>
<property name="serviceInterface" value="example.AccountService"/>
</bean>
這就是我們?cè)诳蛻舳藶橹С诌h(yuǎn)程account服務(wù)所需要做的。Spring將透明的創(chuàng)建一個(gè)調(diào)用器并且通過(guò) RmiServiceExporter 使得account服務(wù)支持遠(yuǎn)程服務(wù)。在客戶端,我們用 RmiProxyFactoryBean 連接它。
Hessian 提供一種基于HTTP的二進(jìn)制遠(yuǎn)程協(xié)議。它是由Caucho開(kāi)發(fā)的,可以在 http://www.caucho.com 找到更多有關(guān)Hessian的信息。
Hessian 使用一個(gè)特定的Servlet通過(guò)HTTP進(jìn)行通訊。使用Spring在Web MVC中就常用的 DispatcherServlet 原 理,可以很容易的配置這樣一個(gè)Servlet來(lái)暴露你的服務(wù)。首先我們要在你的應(yīng)用里創(chuàng)建一個(gè)新的Servlet(下面來(lái)自 web.xml 文件):
<servlet>
<servlet-name>remoting</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>remoting</servlet-name>
<url-pattern>/remoting/*</url-pattern>
</servlet-mapping>
你可能對(duì)Spring的 DispatcherServlet 很 熟悉,這樣你將需要在 'WEB-INF' 目錄中創(chuàng)建一個(gè)名為 'remoting-servlet.xml' (在你的servlet名稱后) 的Spring容器配置上下文。這個(gè)應(yīng)用上下文將在下一節(jié)中里使用。
另外,可以考慮使用Spring中簡(jiǎn)單的 HttpRequestHandlerServlet 。這允許你在根應(yīng)用上下文(默認(rèn)是 'WEB-INF/applicationContext.xml' )中插入遠(yuǎn)程exporter定義。每 個(gè)servlet定義指向特定的exporter bean。在這種情況下,每個(gè)servlet的名稱需要和目標(biāo)exporter bean的名稱相匹配。
在 新創(chuàng)建的 remoting-servlet.xml 應(yīng)用上下文里,我們將創(chuàng)建一個(gè) HessianServiceExporter 來(lái)暴露你的服務(wù):
<bean id="accountService" class="example.AccountServiceImpl">
<!-- any additional properties, maybe a DAO? -->
</bean>
<bean name="/AccountService" class="org.springframework.remoting.caucho.HessianServiceExporter">
<property name="service" ref="accountService"/>
<property name="serviceInterface" value="example.AccountService"/>
</bean>
現(xiàn)在,我們準(zhǔn)備好在客戶端連接服務(wù)了。不必顯示指定處理器的映射,只要使用 BeanNameUrlHandlerMapping 把URL請(qǐng)求映射到服務(wù)上:所以,這個(gè)服務(wù)將在由 bean名稱指明的URL http://HOST:8080/remoting/AccountService 位置進(jìn)行暴露。
另外一種選擇, 在你的根應(yīng)用上下文中創(chuàng)建一個(gè) HessianServiceExporter (比如在 'WEB-INF/applicationContext.xml' 中):
<bean name="accountExporter" class="org.springframework.remoting.caucho.HessianServiceExporter">
<property name="service" ref="accountService"/>
<property name="serviceInterface" value="example.AccountService"/>
</bean>
在后一情況下, 在 'web.xml' 中為 exporter定義一個(gè)相應(yīng)的servlet,也能得到同樣的結(jié)果:這個(gè)exporter映射到request路徑 /remoting/AccountService 。注意這個(gè)servlet名稱需要與目標(biāo)exporter bean的名稱相匹配。
<servlet>
<servlet-name>accountExporter</servlet-name>
<servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>accountExporter</servlet-name>
<url-pattern>/remoting/AccountService</url-pattern>
</servlet-mapping>
使 用 HessianProxyFactoryBean ,我們可以在客戶端連接服務(wù)。同樣的方式對(duì) RMI示例也適用。我們將創(chuàng)建一個(gè)單獨(dú)的bean工廠或者應(yīng)用上下文,而后簡(jiǎn)單地指明下面的bean SimpleObject 將 使用 AccountService 來(lái)管理accounts:
<bean class="example.SimpleObject">
<property name="accountService" ref="accountService"/>
</bean>
<bean id="accountService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
<property name="serviceUrl" value="http://remotehost:8080/remoting/AccountService"/>
<property name="serviceInterface" value="example.AccountService"/>
</bean>
我 們將不會(huì)詳細(xì)討論Burlap,它是一個(gè)基于XML的Hessian替代方案。它的配置和構(gòu)建方法和上述Hessian的一樣。只要把 Hessian 換成 Burlap 就行了。
Hessian 和Burlap的一個(gè)優(yōu)勢(shì)是我們可以容易的使用HTTP Basic認(rèn)證,因?yàn)槎叨际腔贖TTP的。例如,普通HTTP Server安全機(jī)制可以通過(guò)使用 web.xml 安全特性來(lái)應(yīng)用。通常,你不會(huì)為每個(gè)用戶都建立不同的安全證書(shū),而是在 Hessian/BurlapProxyFactoryBean 級(jí) 別共享安全證書(shū)(類似一個(gè)JDBC DataSource )。
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
<property name="interceptors" ref="authorizationInterceptor"/>
</bean>
<bean id="authorizationInterceptor"
class="org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor">
<property name="authorizedRoles" value="administrator,operator"/>
</bean>
這個(gè)例子里我們顯式使用了 BeanNameUrlHandlerMapping , 并設(shè)置了一個(gè)攔截器,只允許管理員和操作員調(diào)用這個(gè)應(yīng)用上下文中提及的bean。
當(dāng) 然,這個(gè)例子沒(méi)有演示靈活的安全設(shè)施。考慮更多有關(guān)安全的問(wèn)題時(shí),請(qǐng)參閱 http://acegisecurity.sourceforge.net Acegi Security System for Spring 。 |
與使用自身序列化機(jī)制的輕量級(jí)協(xié)議Burlap和Hessian相 反,Spring HTTP調(diào)用器使用標(biāo)準(zhǔn)Java序列化機(jī)制來(lái)通過(guò)HTTP暴露業(yè)務(wù)。如果你的參數(shù)或返回值是復(fù)雜類型,并且不能通過(guò)Hessian和Burlap的序列化 機(jī)制進(jìn)行序列化,HTTP調(diào)用器就很有優(yōu)勢(shì)(參閱下一節(jié),選擇遠(yuǎn)程技術(shù)時(shí)的考慮)。
實(shí)際上,Spring可以使用J2SE提供的標(biāo)準(zhǔn)功能或 Commons的HttpClient來(lái)實(shí)現(xiàn)HTTP調(diào)用。如果你需要更先進(jìn),更容易使用的功能,就使用后者。你可以參考 jakarta.apache.org/commons/httpclient 。
為服務(wù)對(duì)象設(shè)置HTTP調(diào)用器和你在Hessian或 Burlap中使用的方式類似。就象為Hessian支持提供的 HessianServiceExporter ,Spring 的HTTP調(diào)用器提供了 org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter 。
為了在Spring Web MVC DispatcherServlet 中 暴露 AccountService (如上所述), 需要在dispatcher的應(yīng)用上下文中使用以下配置:
<bean name="/AccountService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="accountService"/>
<property name="serviceInterface" value="example.AccountService"/>
</bean>
和在Hessian章節(jié)講的一樣,這個(gè)exporter定義將通過(guò) DispatcherServlet 標(biāo) 準(zhǔn)的映射工具暴露出來(lái)。
做為可選項(xiàng), 在你的根應(yīng)用上下文中(比如 'WEB-INF/applicationContext.xml' ) 創(chuàng)建一個(gè) HttpInvokerServiceExporter :
<bean name="accountExporter" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="accountService"/>
<property name="serviceInterface" value="example.AccountService"/>
</bean>
另外,在 'web.xml' 中為這個(gè) exporter定義一個(gè)相應(yīng)的servlet,其名稱與目標(biāo)exporter bean的名稱相匹配:
<servlet>
<servlet-name>accountExporter</servlet-name>
<servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>accountExporter</servlet-name>
<url-pattern>/remoting/AccountService</url-pattern>
</servlet-mapping>
同樣,從客戶端連接業(yè)務(wù)與你使用Hessian或Burlap時(shí)所做的很相似。使用代理,Spring可以將你調(diào)用的HTTP POST請(qǐng)求轉(zhuǎn)換成被暴露服務(wù)的URL。
<bean id="httpInvokerProxy" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property name="serviceUrl" value="http://remotehost:8080/remoting/AccountService"/>
<property name="serviceInterface" value="example.AccountService"/>
</bean>
就象上面說(shuō)的一樣,你可以選擇使用你想使用的HTTP客戶端。缺省情況下, HttpInvokerProxy 使 用J2SE的HTTP功能,但是你也可以通過(guò)設(shè)置 httpInvokerRequestExecutor 屬 性選擇使用Commons HttpClient :
<property name="httpInvokerRequestExecutor">
<bean class="org.springframework.remoting.httpinvoker.CommonsHttpInvokerRequestExecutor"/>
</property>
Spring為標(biāo)準(zhǔn)Java web服務(wù)API提供了全面的支持:
-
使用 JAX-RPC暴露web服務(wù)
-
使用JAX-RPC訪問(wèn)web服務(wù)
-
使用JAX-WS暴露 web服務(wù)
-
使用JAX-WS訪問(wèn)web服務(wù)
為 什么有2個(gè)標(biāo)準(zhǔn)的Java web服務(wù)APIs? JAX-RPC 1.1 在J2EE 1.4 中是標(biāo)準(zhǔn)的web服務(wù)API。正像其名稱所示,它關(guān)注于RPC綁定而且在最近幾年越來(lái)越不流行。最終被Java EE 5中的JAX-WS 2.0所取代,JAX-WS 2.0不但在綁定方面更靈活,而且也是完全基于annotation的。JAX-WS 2.1也被包含在Java 6中(更詳細(xì)的說(shuō)是在Sun JDK 1.6.0_04和更高版本中,低版本的Sun JDK 1.6.0包含JAX-WS 2.0),它與JDK內(nèi)置的HTTP服務(wù)器集成。 Spring 同時(shí)支持兩個(gè)標(biāo)準(zhǔn)Java web服務(wù)API。選擇誰(shuí)主要看運(yùn)行平臺(tái):在JDK 1.4 / J2EE 1.4上,唯一的選擇是JAX-RPC。在Java EE 5 / Java 6上顯然應(yīng)該選JAX-WS。運(yùn)行Java 5的J2EE 1.4環(huán)境上,你可以選擇插入一個(gè)JAX-WS provider;請(qǐng)查看你的J2EE服務(wù)器文檔。 |
除了在Spring Core中支持JAX-RPC and JAX-WS,Spring portfolio也提供了一種特性 Spring Web Services ,一個(gè)為優(yōu)先授權(quán)和文檔驅(qū)動(dòng)的web服務(wù)所提供的方案 - 非常建議用來(lái)創(chuàng)建高級(jí)并具有前瞻性的web服務(wù)。 XFire 是最后但不是唯一的Spring 內(nèi)置支持可以讓你將Spring管理的bean暴露為web服務(wù)的方式。
Spring為JAX-RPC servlet的端點(diǎn)實(shí)現(xiàn)提供了一個(gè)方便的基類 - ServletEndpointSupport . 未來(lái)暴露我們的 AccountService 我們擴(kuò)展Spring的 ServletEndpointSupport 類并在這里實(shí)現(xiàn)了我們的業(yè)務(wù)邏輯,通常將調(diào)用交給業(yè)務(wù)層。
/**
* JAX-RPC compliant RemoteAccountService implementation that simply delegates
* to the AccountService implementation in the root web application context.
*
* This wrapper class is necessary because JAX-RPC requires working with dedicated
* endpoint classes. If an existing service needs to be exported, a wrapper that
* extends ServletEndpointSupport for simple application context access is
* the simplest JAX-RPC compliant way.
*
* This is the class registered with the server-side JAX-RPC implementation.
* In the case of Axis, this happens in "server-config.wsdd" respectively via
* deployment calls. The web service engine manages the lifecycle of instances
* of this class: A Spring application context can just be accessed here.
*/ import org.springframework.remoting.jaxrpc.ServletEndpointSupport;
public class AccountServiceEndpoint extends ServletEndpointSupport implements RemoteAccountService {
private AccountService biz;
protected void onInit() {
this.biz = (AccountService) getWebApplicationContext().getBean("accountService");
}
public void insertAccount(Account acc) throws RemoteException {
biz.insertAccount(acc);
}
public Account[] getAccounts(String name) throws RemoteException {
return biz.getAccounts(name);
}
}
AccountServletEndpoint需要在Spring中同一個(gè)上下文的web應(yīng)用里運(yùn)行,以獲得對(duì)Spring的訪問(wèn)能 力。如果使用Axis,把 AxisServlet 定義復(fù)制到你的 'web.xml' 中,并且在 'server-config.wsdd' 中 設(shè)置端點(diǎn)(或使用發(fā)布工具)。參看JPetStore這個(gè)例子中 OrderService 是 如何用Axis發(fā)布成一個(gè)Web服務(wù)的。
Spring提供了兩個(gè)工廠bean用來(lái)創(chuàng) 建Web服務(wù)代理, LocalJaxRpcServiceFactoryBean 和 JaxRpcPortProxyFactoryBean 。前者只返回一個(gè)JAX-RPC服務(wù)類供我們使 用。后者是一個(gè)全功能的版本,可以返回一個(gè)實(shí)現(xiàn)我們業(yè)務(wù)服務(wù)接口的代理。本例中,我們使用后者來(lái)為前面段落中暴露的 AccountService 端點(diǎn)創(chuàng)建一個(gè)代理。你將看到Spring對(duì)Web服務(wù)提供了極好的 支持,只需要很少的代碼 - 大多數(shù)都是通過(guò)類似下面的Spring配置文件:
<bean id="accountWebService" class="org.springframework.remoting.jaxrpc.JaxRpcPortProxyFactoryBean">
<property name="serviceInterface" value="example.RemoteAccountService"/>
<property name="wsdlDocumentUrl" value="http://localhost:8080/account/services/accountService?WSDL"/>
<property name="namespaceUri" value="http://localhost:8080/account/services/accountService"/>
<property name="serviceName" value="AccountService"/>
<property name="portName" value="AccountPort"/>
</bean>
serviceInterface 是我們客戶端將使用 的遠(yuǎn)程業(yè)務(wù)接口。 wsdlDocumentUrl 是WSDL文件的URL. Spring需要用它作為啟動(dòng)點(diǎn)來(lái)創(chuàng)建JAX-RPC服務(wù)。 namespaceUri 對(duì)應(yīng).wsdl文件中的targetNamespace。 serviceName 對(duì)應(yīng).wsdl文件中的服務(wù)名。 portName 對(duì)應(yīng).wsdl文件中的端口號(hào)。
現(xiàn)在我們可以很方便的訪問(wèn)web服務(wù),因?yàn)槲覀冇幸粋€(gè)可以將它暴露為 RemoteAccountService 接 口的bean工廠。我們可以在Spring中這樣使用:
<bean id="client" class="example.AccountClientImpl">
...
<property name="service" ref="accountWebService"/>
</bean>
從客戶端代碼上看,除了它拋出 RemoteException , 我們可以把這個(gè)web服務(wù)當(dāng)成一個(gè)普通的類進(jìn)行訪,。
public class AccountClientImpl {
private RemoteAccountService service;
public void setService(RemoteAccountService service) {
this.service = service;
}
public void foo() {
try {
service.insertAccount(...);
}
catch (RemoteException ex) {
// ouch
}
}
}
我們可以不檢查受控異常 RemoteException ,因?yàn)?Spring將它自動(dòng)轉(zhuǎn)換成相應(yīng)的非受控異常 RemoteException 。這也需要 我們提供一個(gè)非RMI的接口。現(xiàn)在配置文件如下:
<bean id="accountWebService" class="org.springframework.remoting.jaxrpc.JaxRpcPortProxyFactoryBean">
<property name="serviceInterface" value="example.AccountService"/>
<property name="portInterface" value="example.RemoteAccountService"/>
</bean>
我們的 serviceInterface 變成了非 RMI接口。我們的RMI接口現(xiàn)在使用 portInterface 屬性來(lái)定義。我們的客戶端代碼可以 避免處理異常 java.rmi.RemoteException :
public class AccountClientImpl {
private AccountService service;
public void setService(AccountService service) {
this.service = service;
}
public void foo() {
service.insertAccount(...);
}
}
請(qǐng)注意你也可以去掉"portInterface"部分并指定一個(gè)普通業(yè)務(wù)接口作為"serviceInterface"。這樣 JaxRpcPortProxyFactoryBean 將自動(dòng)切換到JAX-RPC "動(dòng)態(tài)調(diào)用接口", 不使用固定端口存根來(lái)進(jìn)行動(dòng)態(tài)調(diào)用。這樣做的好處是你甚至不需要使用一個(gè)RMI相關(guān)的Java接口(比如在非Java的目標(biāo)web服務(wù)中);你只需要一個(gè) 匹配的業(yè)務(wù)接口。查看 JaxRpcPortProxyFactoryBean 的javadoc來(lái) 了解運(yùn)行時(shí)實(shí)行的細(xì)節(jié)。
T為了傳遞類似 Account 等復(fù)雜對(duì)象,我們必須在客戶端注冊(cè)bean映射。
在 服務(wù)器端通常在 'server-config.wsdd' 中使用Axis進(jìn)行bean映射注冊(cè)。 |
我 們將使用Axis在客戶端注冊(cè)bean映射。為此,我們需要通過(guò)程序注冊(cè)這個(gè)bean映射:
public class AxisPortProxyFactoryBean extends JaxRpcPortProxyFactoryBean {
protected void postProcessJaxRpcService(Service service) {
TypeMappingRegistry registry = service.getTypeMappingRegistry();
TypeMapping mapping = registry.createTypeMapping();
registerBeanMapping(mapping, Account.class, "Account");
registry.register("http://schemas.xmlsoap.org/soap/encoding/", mapping);
}
protected void registerBeanMapping(TypeMapping mapping, Class type, String name) {
QName qName = new QName("http://localhost:8080/account/services/accountService", name);
mapping.register(type, qName,
new BeanSerializerFactory(type, qName),
new BeanDeserializerFactory(type, qName));
}
}
本節(jié)中,我們將注冊(cè)自己的 javax.rpc.xml.handler.Handler 到Web服務(wù)代理,這樣我們可以在SOAP消息被發(fā)送前執(zhí)行定制的代碼。 Handler 是 一個(gè)回調(diào)接口。 jaxrpc.jar 中有個(gè)方便的基類 javax.rpc.xml.handler.GenericHandler 供 我們繼承使用:
public class AccountHandler extends GenericHandler {
public QName[] getHeaders() {
return null;
}
public boolean handleRequest(MessageContext context) {
SOAPMessageContext smc = (SOAPMessageContext) context;
SOAPMessage msg = smc.getMessage();
try {
SOAPEnvelope envelope = msg.getSOAPPart().getEnvelope();
SOAPHeader header = envelope.getHeader();
...
}
catch (SOAPException ex) {
throw new JAXRPCException(ex);
}
return true;
}
}
我們現(xiàn)在要做的就是把AccountHandler注冊(cè)到JAX-RPC服務(wù),這樣它可以在消息被發(fā)送前調(diào)用 handleRequest(..) 。Spring目前對(duì)注冊(cè)處理方法還不提供聲明式支持,所以我們必 須使用編程方式。但是Spring中這很容易實(shí)現(xiàn),我們只需覆寫(xiě)專門(mén)為此設(shè)計(jì)的 postProcessJaxRpcService(..) 方法:
public class AccountHandlerJaxRpcPortProxyFactoryBean extends JaxRpcPortProxyFactoryBean {
protected void postProcessJaxRpcService(Service service) {
QName port = new QName(this.getNamespaceUri(), this.getPortName());
List list = service.getHandlerRegistry().getHandlerChain(port);
list.add(new HandlerInfo(AccountHandler.class, null, null));
logger.info("Registered JAX-RPC AccountHandler on port " + port);
}
}
最后,我們要記得更改Spring配置文件來(lái)使用我們的工廠bean:
<bean id="accountWebService" class="example.AccountHandlerJaxRpcPortProxyFactoryBean">
...
</bean>
Spring為JAX-WS servlet端點(diǎn)實(shí)現(xiàn)提供了一個(gè)方便的基類 - SpringBeanAutowiringSupport 。 要暴露我們的 AccountService 接口,我們可以擴(kuò)展Spring的 SpringBeanAutowiringSupport 類并實(shí)現(xiàn)我們的業(yè)務(wù)邏輯,通常把調(diào)用交給業(yè)務(wù) 層。我們將簡(jiǎn)單的使用Spring 2.5的 @Autowired 注解來(lái)聲明依賴于Spring管理 的bean。
/**
* JAX-WS compliant AccountService implementation that simply delegates
* to the AccountService implementation in the root web application context.
*
* This wrapper class is necessary because JAX-WS requires working with dedicated
* endpoint classes. If an existing service needs to be exported, a wrapper that
* extends SpringBeanAutowiringSupport for simple Spring bean autowiring (through
* the @Autowired annotation) is the simplest JAX-WS compliant way.
*
* This is the class registered with the server-side JAX-WS implementation.
* In the case of a Java EE 5 server, this would simply be defined as a servlet
* in web.xml, with the server detecting that this is a JAX-WS endpoint and reacting
* accordingly. The servlet name usually needs to match the specified WS service name.
*
* The web service engine manages the lifecycle of instances of this class.
* Spring bean references will just be wired in here.
*/ import org.springframework.web.context.support.SpringBeanAutowiringSupport;
@WebService(serviceName="AccountService")
public class AccountServiceEndpoint extends SpringBeanAutowiringSupport {
@Autowired
private AccountService biz;
@WebMethod
public void insertAccount(Account acc) {
biz.insertAccount(acc);
}
@WebMethod
public Account[] getAccounts(String name) {
return biz.getAccounts(name);
}
}
為了能夠讓Spring上下文使用Spring設(shè)施,我們的 AccountServletEndpoint 類 需要運(yùn)行在同一個(gè)web應(yīng)用中。在Java EE 5環(huán)境中這是默認(rèn)的情況,它使用JAX-WS servlet端點(diǎn)安裝標(biāo)準(zhǔn)契約。詳情請(qǐng)參閱Java EE 5 web服務(wù)教程。
Sun JDK 1.6提供的內(nèi)置JAX-WS provider 使用內(nèi)置的HTTP服務(wù)器來(lái)暴露web服務(wù)。Spring的 SimpleJaxWsServiceExporter 類 檢測(cè)所有在Spring應(yīng)用上下文中配置的l @WebService 注解bean,然后通過(guò)默認(rèn)的JAX-WS服務(wù)器(JDK 1.6 HTTP服務(wù)器)來(lái)暴露它們。
在這種場(chǎng)景下,端點(diǎn)實(shí)例將被作為 Spring bean來(lái)定義和管理。它們將使用JAX-WS來(lái)注冊(cè),但其生命周期將一直跟隨Spring應(yīng)用上下文。這意味著Spring的顯示依賴注入可用于端點(diǎn)實(shí) 例。當(dāng)然通過(guò) @Autowired 來(lái)進(jìn)行注解驅(qū)動(dòng)的注入也可以正常工作。
<bean class="org.springframework.remoting.jaxws.SimpleJaxWsServiceExporter">
<property name="baseAddress" value="http://localhost:9999/"/>
</bean>
<bean id="accountServiceEndpoint" class="example.AccountServiceEndpoint">
...
</bean>
...
AccountServiceEndpoint 類可能源自Spring 的 SpringBeanAutowiringSupport 類,也可能不是。因?yàn)檫@里的端點(diǎn)是 由Spring完全管理的bean。這意味著端點(diǎn)實(shí)現(xiàn)可能像下面這樣沒(méi)有任何父類定義 - 而且Spring的 @Autowired 配 置注解仍然能夠使用:
@WebService(serviceName="AccountService")
public class AccountServiceEndpoint {
@Autowired
private AccountService biz;
@WebMethod
public void insertAccount(Account acc) {
biz.insertAccount(acc);
}
@WebMethod
public Account[] getAccounts(String name) {
return biz.getAccounts(name);
}
}
Sun的JAX-WS RI被作為GlassFish項(xiàng)目的一部分來(lái)開(kāi)發(fā),它使用了Spring支持來(lái)作為JAX-WS Commons項(xiàng)目的一部分。這允許把JAX-WS端點(diǎn)作為Spring管理的bean來(lái)定義。這與前面章節(jié)討論的單獨(dú)模式類似 - 但這次是在Servlet環(huán)境中。 注意這在Java EE 5環(huán)境中是不可遷移的,建議在沒(méi)有EE的web應(yīng)用環(huán)境如Tomcat中嵌入JAX-WS RI。
與標(biāo)準(zhǔn)的暴露基 于servlet的端點(diǎn)方式不同之處在于端點(diǎn)實(shí)例的生命周期將被Spring管理。這里在 web.xml 將 只有一個(gè)JAX-WS servlet定義。在標(biāo)準(zhǔn)的Java EE 5風(fēng)格中(如上所示),你將對(duì)每個(gè)服務(wù)端點(diǎn)定義一個(gè)servlet,每個(gè)服務(wù)端點(diǎn)都代理到Spring bean (通過(guò)使用 @Autowired ,如上所示)。
關(guān)于安裝和使用詳情請(qǐng)查閱 https://jax-ws-commons.dev.java.net/spring/ 。
類似JAX-RPC支持,Spring提供了 2個(gè)工廠bean來(lái)創(chuàng)建JAX-WS web服務(wù)代理,它們是 LocalJaxWsServiceFactoryBean 和 JaxWsPortProxyFactoryBean 。前一個(gè)只能返回一個(gè)JAX-WS服務(wù)對(duì)象來(lái)讓我 們使用。后面的是可以返回我們業(yè)務(wù)服務(wù)接口的代理實(shí)現(xiàn)的完整版本。這個(gè)例子中我們使用后者來(lái)為 AccountService 端 點(diǎn)再創(chuàng)建一個(gè)代理:
<bean id="accountWebService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
<property name="serviceInterface" value="example.AccountService"/>
<property name="wsdlDocumentUrl" value="http://localhost:8080/account/services/accountService?WSDL"/>
<property name="namespaceUri" value="http://localhost:8080/account/services/accountService"/>
<property name="serviceName" value="AccountService"/>
<property name="portName" value="AccountPort"/>
</bean>
serviceInterface 是我們客戶端將使用 的遠(yuǎn)程業(yè)務(wù)接口。 wsdlDocumentUrl 是WSDL文件的URL. Spring需要用它作為啟動(dòng)點(diǎn)來(lái)創(chuàng)建JAX-RPC服務(wù)。 namespaceUri 對(duì)應(yīng).wsdl文件中的targetNamespace。 serviceName 對(duì)應(yīng).wsdl文件中的服務(wù)名。 portName 對(duì)應(yīng).wsdl文件中的端口號(hào)。
現(xiàn)在我們可以很方便的訪問(wèn)web服務(wù),因?yàn)槲覀冇幸粋€(gè)可以將它暴露為 AccountService 接 口的bean工廠。我們可以在Spring中這樣使用:
<bean id="client" class="example.AccountClientImpl">
...
<property name="service" ref="accountWebService"/>
</bean>
從客戶端代碼上我們可以把這個(gè)web服務(wù)當(dāng)成一個(gè)普通的類進(jìn)行訪問(wèn):
public class AccountClientImpl {
private AccountService service;
public void setService(AccountService service) {
this.service = service;
}
public void foo() {
service.insertAccount(...);
}
}
注意: 上面被稍微簡(jiǎn)化了,因?yàn)镴AX-WS需要端點(diǎn)接口及實(shí)現(xiàn)類來(lái)使用 @WebService , @SOAPBinding 等注解。 這意味著你不能簡(jiǎn)單的使用普通的Java接口和實(shí)現(xiàn)來(lái)作為JAX-WS端點(diǎn),你需要首先對(duì)它們進(jìn)行相應(yīng)的注解。這些需求詳情請(qǐng)查閱JAX-WS文檔。
XFire是一個(gè)Codehaus提供的輕量級(jí)SOAP庫(kù)。 暴露XFire是通過(guò)XFire自帶的context,這個(gè)context將和RemoteExporter風(fēng)格的bean相結(jié)合,后者需要被加入到在你 的 WebApplicationContext 中。對(duì)于所有讓你來(lái)暴露服務(wù)的方法,你需 要?jiǎng)?chuàng)建一個(gè) DispatcherServlet 類并有相應(yīng)的 WebApplicationContext 來(lái)封裝你將要暴露的服務(wù):
<servlet>
<servlet-name>xfire</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
你還必須鏈接X(jué)Fire配置。這是通過(guò)增加一個(gè)context文件到由 ContextLoaderListener (或者 ContextLoaderServlet ) 加載的 contextConfigLocations 參數(shù)中。
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:org/codehaus/xfire/spring/xfire.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
在你加入一個(gè)Servlet映射后(映射 /* 到 上面定義的XFire Servlet),你只需要增加一個(gè)額外的bean來(lái)使用XFire暴露服務(wù)。例如,在 'xfire-servlet.xml' 中增加如下配置:
<beans>
<bean name="/Echo" class="org.codehaus.xfire.spring.remoting.XFireExporter">
<property name="serviceInterface" value="org.codehaus.xfire.spring.Echo"/>
<property name="serviceBean">
<bean class="org.codehaus.xfire.spring.EchoImpl"/>
</property>
<!-- the XFire bean is defined in the xfire.xml file -->
<property name="xfire" ref="xfire"/>
</bean>
</beans>
XFire處理了其他的事情。它檢查你的服務(wù)接口并產(chǎn)生一個(gè)WSDL文件。這里的部分文檔來(lái)自XFire 網(wǎng)站,要了解更多有關(guān)XFire Spring的集成請(qǐng)?jiān)L問(wèn) docs.codehaus.org/display/XFIRE/Spring 。
使 用JMS來(lái)作為底層的通信協(xié)議透明暴露服務(wù)也是可能的。Spring框架中對(duì)JMS的遠(yuǎn)程支持也很基礎(chǔ) - 它在 同一線程 和 同一個(gè)非事務(wù) Session 上發(fā)送和接收,這些吞吐量將非常依賴于實(shí)現(xiàn)。
The following interface is used on both the server and the client side.
下 面的接口可同時(shí)用在服務(wù)端和客戶端。
package com.foo;
public interface CheckingAccountService {
public void cancelAccount(Long accountId);
}
對(duì)于上面接口的使用在服務(wù)的端簡(jiǎn)單實(shí)現(xiàn)如下。
package com.foo;
public class SimpleCheckingAccountService implements CheckingAccountService {
public void cancelAccount(Long accountId) {
System.out.println("Cancelling account [" + accountId + "]");
}
}
這個(gè)包含JMS設(shè)施的bean的配置文件可同時(shí)用在客戶端和服務(wù)端。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://ep-t43:61616"/>
</bean>
<bean id="queue" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg value="mmm"/>
</bean>
</beans>
在 服務(wù)端你只需要使用 JmsInvokerServiceExporter 來(lái)暴露服務(wù)對(duì)象。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="checkingAccountService"
class="org.springframework.jms.remoting.JmsInvokerServiceExporter">
<property name="serviceInterface" value="com.foo.CheckingAccountService"/>
<property name="service">
<bean class="com.foo.SimpleCheckingAccountService"/>
</property>
</bean>
<bean class="org.springframework.jms.listener.SimpleMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="queue"/>
<property name="concurrentConsumers" value="3"/>
<property name="messageListener" ref="checkingAccountService"/>
</bean>
</beans>
package com.foo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Server {
public static void main(String[] args) throws Exception {
new ClassPathXmlApplicationContext(new String[]{"com/foo/server.xml", "com/foo/jms.xml"});
}
}
客 戶端僅僅需要?jiǎng)?chuàng)建一個(gè)客戶端代理來(lái)實(shí)現(xiàn)上面的接口( CheckingAccountService )。 根據(jù)后面的bean定義創(chuàng)建的結(jié)果對(duì)象可以被注入到其它客戶端對(duì)象中,而這個(gè)代理會(huì)負(fù)責(zé)通過(guò)JMS將調(diào)用轉(zhuǎn)發(fā)到服務(wù)端。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="checkingAccountService"
class="org.springframework.jms.remoting.JmsInvokerProxyFactoryBean">
<property name="serviceInterface" value="com.foo.CheckingAccountService"/>
<property name="connectionFactory" ref="connectionFactory"/>
<property name="queue" ref="queue"/>
</bean>
</beans>
package com.foo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Client {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
new String[] {"com/foo/client.xml", "com/foo/jms.xml"});
CheckingAccountService service = (CheckingAccountService) ctx.getBean("checkingAccountService");
service.cancelAccount(new Long(10));
}
}
你可能也希望研究 Lingo 項(xiàng)目提供的支持,它(引用到主頁(yè)) “ ... 是一個(gè)基于輕量級(jí)POJO的遠(yuǎn)程核消息代碼庫(kù),它使用并擴(kuò)展了Spring框架的遠(yuǎn)程代碼庫(kù)以支持JMS。 ”
對(duì) 遠(yuǎn)程接口不實(shí)現(xiàn)自動(dòng)探測(cè)的主要原因是防止產(chǎn)生太多的遠(yuǎn)程調(diào)用。目標(biāo)對(duì)象有可能實(shí)現(xiàn)的是類似 InitializingBean 或 者 DisposableBean 這樣的內(nèi)部回調(diào)接口,而這些是不希望暴露給調(diào)用者的。
提 供一個(gè)所有接口都被目標(biāo)實(shí)現(xiàn)的代理通常和本地情況無(wú)關(guān)。但是當(dāng)暴露一個(gè)遠(yuǎn)程服務(wù)時(shí),你應(yīng)該只暴露特定的用于遠(yuǎn)程使用的服務(wù)接口。除了內(nèi)部回調(diào)接口,目標(biāo)有 可能實(shí)現(xiàn)了多個(gè)業(yè)務(wù)接口,而往往只有一個(gè)是用于遠(yuǎn)程調(diào)用的。出于這些原因,我們 要求 指定這樣的服務(wù)接口。
這是在配置方便性和意外暴露內(nèi)部方法的危險(xiǎn)性之間作的平衡。總是指明服務(wù)接口并不要花太大代價(jià),并可以讓你控制需暴 露方法從而更加安全。
這 里提到的每種技術(shù)都有它的缺點(diǎn)。你在選擇一種技術(shù)時(shí),應(yīng)該仔細(xì)考慮你的需要和所暴露的服務(wù)及你在遠(yuǎn)程訪問(wèn)時(shí)傳送的對(duì)象。
當(dāng)使用RMI時(shí),通過(guò)HTTP協(xié)議訪問(wèn)對(duì)象是不可能的,除非你用HTTP包裹RMI流。RMI是一種重量級(jí)協(xié)議,因?yàn)樗С终麄€(gè)對(duì)象的序列化,當(dāng)要求網(wǎng)絡(luò) 上傳輸復(fù)雜數(shù)據(jù)結(jié)構(gòu)時(shí)這是非常重要的。然而,RMI-JRMP只能綁定到Java客戶端:它是一種Java-to-Java的遠(yuǎn)程訪問(wèn)解決方案。
如 果你需要基于HTTP的遠(yuǎn)程訪問(wèn)而且還要求使用Java序列化,Spring的HTTP調(diào)用器是一個(gè)很好的選擇。它和RMI調(diào)用器使用相同的基礎(chǔ)設(shè)施,僅 僅使用HTTP作為傳輸方式。注意HTTP調(diào)用器不僅只能用在Java-to-Java的遠(yuǎn)程訪問(wèn),而且在客戶端和服務(wù)器端都必須使用 Spring。(Spring為非RMI接口提供的RMI調(diào)用器也要求客戶端和服務(wù)器端都使用Spring)
在使用服務(wù)集群和需要JMS代 理(JMS broker)來(lái)處理負(fù)載均衡及發(fā)現(xiàn)和自動(dòng)-失敗恢復(fù)服務(wù)時(shí)JMS是很有用的。缺省情況下,在使用JMS遠(yuǎn)程服務(wù)時(shí)使用Java序列化,但是JMS提供者 也可以使用不同的機(jī)制例如XStream來(lái)讓服務(wù)器用其他技術(shù)。
最后但不僅限于此,相對(duì)于RMI,EJB有一個(gè)優(yōu)點(diǎn)是它支持標(biāo)準(zhǔn)的基于角色 的認(rèn)證和授權(quán),以及遠(yuǎn)程事務(wù)傳遞。用RMI調(diào)用器或HTTP調(diào)用器來(lái)支持安全上下文的傳遞是可能的,雖然這不由核心Spring提供:Spring提供了 合適的鉤子來(lái)插入第三方或定制的解決方案。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
