http://ajava.org/online/spring2.5/html/remoting.html#remoting-rmi
簡介
Spring 為各種遠程訪問技術的集成提供了整合類。Spring使得開發具有遠程訪問功能的服務變得相當容易,而這些遠程訪問服務由普通Spring POJO實現。目前,Spring支持四種遠程技術:
-
遠程方法調用(RMI) 。通過使用 RmiProxyFactoryBean 和 RmiServiceExporter ,Spring同時支持傳統的RMI(使用 java.rmi.Remote 接口和 java.rmi.RemoteException ) 和通過RMI調用器實現的透明遠程調用(支持任何Java接口)。
-
Spring的HTTP調用器 。Spring提供了一種允 許通過HTTP進行Java串行化的特殊遠程調用策略,它支持任意Java接口(就像RMI調用器)。相對應的支持類是 HttpInvokerProxyFactoryBean 和 HttpInvokerServiceExporter 。
-
Hessian 。通過 HessianProxyFactoryBean 和 HessianServiceExporter , 可以使用Caucho提供的基于HTTP的輕量級二進制協議來透明地暴露服務。
-
Burlap 。 Burlap是Caucho基于XML用來替代Hessian的項目。Spring提供了諸如 BurlapProxyFactoryBean 和 BurlapServiceExporter 的支持類。
-
JAX RPC 。Spring通過JAX- RPC(J2EE 1.4's wweb service API)為Web services提供遠程服務支持。
-
JAX-WS . Spring通過(在Java EE 5和Java 6中引入的JAX-RPC繼承)為遠程Web Services提供支持。
-
JMS . 通過 JmsInvokerServiceExporter 和 JmsInvokerProxyFactoryBean 使用JMS做為底層協議提供遠程服務.
在 討論Spring對遠程訪問的支持時,我們將使用下面的域模型和對應的服務:
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;
}
// 該實現目前什么事情也不做
public class AccountServiceImpl implements AccountService {
public void insertAccount(Account acc) {
// 做一些事情……
}
public List getAccounts(String name) {
// 做一些事情……
}
}
我們將從使用RMI把服務暴露給遠程客戶端開始,同時探討RMI的一些缺點。然后我們將演示一個使用Hessian的例子。
使用Spring的RMI支持,你可以通過RMI基礎設施透 明的暴露你的服務。設置好Spring的RMI支持后,你會看到一個和遠程EJB接口類似的配置,只是沒有對安全上下文傳遞和遠程事務傳遞的標準支持。當 使用RMI調用器時,Spring對這些額外的調用上下文提供了鉤子,你可以在此插入安全框架或者定制的安全證書。
使 用 RmiServiceExporter ,我們可以把AccountService對象的接口暴 露成RMI對象??梢允褂? RmiProxyFactoryBean 或者在傳統RMI服務中使用普通RMI來訪問該接口。 RmiServiceExporter 顯式地支持使用RMI調用器暴露任何非RMI的服務。
當然,我們首先需要在Spring容器中設置我們的服務:
<bean id="accountService" class="example.AccountServiceImpl">
<!--其他屬性,或者一個DAO對象?-->
</bean>
然后我們要使用 RmiServiceExporter 來 暴露我們的服務:
<bean class="org.springframework.remoting.rmi.RmiServiceExporter">
<!-- 不一定要與要輸出的bean同名-->
<property name="serviceName" value="AccountService"/>
<property name="service" ref="accountService"/>
<property name="serviceInterface" value="example.AccountService"/>
<!-- 默認為1199-->
<property name="registryPort" value="1199"/>
</bean>
正如你所見,我們覆蓋了RMI注冊的端口號。通常你的應用服務器也會維護RMI注冊,最好不要和它沖突。更 進一步來說,服務名是用來綁定服務的。所以本例中,服務綁定在 rmi://HOST:1199/AccountService 。 在客戶端我們將使用這個URL來鏈接到服務。
servicePort 屬性被省略了(它的默認值為0).這表示在與服務通信時將使用匿名端口. |
我 們的客戶端是一個使用 AccountService 來管理account的簡單對象:
public class SimpleObject {
private AccountService accountService;
public void setAccountService(AccountService accountService) {
this.accountService = accountService;
}
}
為了把服務連接到客戶端上,我們將創建一個單獨的Spring容器,包含這個簡單對象和鏈接配置位的服務:
<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>
這就是我們在客戶端為支持遠程account服務所需要做的。Spring將透明的創建一個調用器并且通過 RmiServiceExporter 使得account服務支持遠程服務。在客戶端,我們用 RmiProxyFactoryBean 連接它。
Hessian 提供一種基于HTTP的二進制遠程協議。它是由Caucho開發的,可以在 http://www.caucho.com 找到更多有關Hessian的信息。
Hessian 使用一個特定的Servlet通過HTTP進行通訊。使用Spring在Web MVC中就常用的 DispatcherServlet 原 理,可以很容易的配置這樣一個Servlet來暴露你的服務。首先我們要在你的應用里創建一個新的Servlet(下面來自 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>
你可能對Spring的 DispatcherServlet 很 熟悉,這樣你將需要在 'WEB-INF' 目錄中創建一個名為 'remoting-servlet.xml' (在你的servlet名稱后) 的Spring容器配置上下文。這個應用上下文將在下一節中里使用。
另外,可以考慮使用Spring中簡單的 HttpRequestHandlerServlet 。這允許你在根應用上下文(默認是 'WEB-INF/applicationContext.xml' )中插入遠程exporter定義。每 個servlet定義指向特定的exporter bean。在這種情況下,每個servlet的名稱需要和目標exporter bean的名稱相匹配。
在 新創建的 remoting-servlet.xml 應用上下文里,我們將創建一個 HessianServiceExporter 來暴露你的服務:
<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>
現在,我們準備好在客戶端連接服務了。不必顯示指定處理器的映射,只要使用 BeanNameUrlHandlerMapping 把URL請求映射到服務上:所以,這個服務將在由 bean名稱指明的URL http://HOST:8080/remoting/AccountService 位置進行暴露。
另外一種選擇, 在你的根應用上下文中創建一個 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定義一個相應的servlet,也能得到同樣的結果:這個exporter映射到request路徑 /remoting/AccountService 。注意這個servlet名稱需要與目標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 ,我們可以在客戶端連接服務。同樣的方式對 RMI示例也適用。我們將創建一個單獨的bean工廠或者應用上下文,而后簡單地指明下面的bean SimpleObject 將 使用 AccountService 來管理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>
我 們將不會詳細討論Burlap,它是一個基于XML的Hessian替代方案。它的配置和構建方法和上述Hessian的一樣。只要把 Hessian 換成 Burlap 就行了。
Hessian 和Burlap的一個優勢是我們可以容易的使用HTTP Basic認證,因為二者都是基于HTTP的。例如,普通HTTP Server安全機制可以通過使用 web.xml 安全特性來應用。通常,你不會為每個用戶都建立不同的安全證書,而是在 Hessian/BurlapProxyFactoryBean 級 別共享安全證書(類似一個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>
這個例子里我們顯式使用了 BeanNameUrlHandlerMapping , 并設置了一個攔截器,只允許管理員和操作員調用這個應用上下文中提及的bean。
當 然,這個例子沒有演示靈活的安全設施??紤]更多有關安全的問題時,請參閱 http://acegisecurity.sourceforge.net Acegi Security System for Spring 。 |
與使用自身序列化機制的輕量級協議Burlap和Hessian相 反,Spring HTTP調用器使用標準Java序列化機制來通過HTTP暴露業務。如果你的參數或返回值是復雜類型,并且不能通過Hessian和Burlap的序列化 機制進行序列化,HTTP調用器就很有優勢(參閱下一節,選擇遠程技術時的考慮)。
實際上,Spring可以使用J2SE提供的標準功能或 Commons的HttpClient來實現HTTP調用。如果你需要更先進,更容易使用的功能,就使用后者。你可以參考 jakarta.apache.org/commons/httpclient 。
為服務對象設置HTTP調用器和你在Hessian或 Burlap中使用的方式類似。就象為Hessian支持提供的 HessianServiceExporter ,Spring 的HTTP調用器提供了 org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter 。
為了在Spring Web MVC DispatcherServlet 中 暴露 AccountService (如上所述), 需要在dispatcher的應用上下文中使用以下配置:
<bean name="/AccountService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="accountService"/>
<property name="serviceInterface" value="example.AccountService"/>
</bean>
和在Hessian章節講的一樣,這個exporter定義將通過 DispatcherServlet 標 準的映射工具暴露出來。
做為可選項, 在你的根應用上下文中(比如 'WEB-INF/applicationContext.xml' ) 創建一個 HttpInvokerServiceExporter :
<bean name="accountExporter" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="accountService"/>
<property name="serviceInterface" value="example.AccountService"/>
</bean>
另外,在 'web.xml' 中為這個 exporter定義一個相應的servlet,其名稱與目標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>
同樣,從客戶端連接業務與你使用Hessian或Burlap時所做的很相似。使用代理,Spring可以將你調用的HTTP POST請求轉換成被暴露服務的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>
就象上面說的一樣,你可以選擇使用你想使用的HTTP客戶端。缺省情況下, HttpInvokerProxy 使 用J2SE的HTTP功能,但是你也可以通過設置 httpInvokerRequestExecutor 屬 性選擇使用Commons HttpClient :
<property name="httpInvokerRequestExecutor">
<bean class="org.springframework.remoting.httpinvoker.CommonsHttpInvokerRequestExecutor"/>
</property>
Spring為標準Java web服務API提供了全面的支持:
-
使用 JAX-RPC暴露web服務
-
使用JAX-RPC訪問web服務
-
使用JAX-WS暴露 web服務
-
使用JAX-WS訪問web服務
為 什么有2個標準的Java web服務APIs? JAX-RPC 1.1 在J2EE 1.4 中是標準的web服務API。正像其名稱所示,它關注于RPC綁定而且在最近幾年越來越不流行。最終被Java EE 5中的JAX-WS 2.0所取代,JAX-WS 2.0不但在綁定方面更靈活,而且也是完全基于annotation的。JAX-WS 2.1也被包含在Java 6中(更詳細的說是在Sun JDK 1.6.0_04和更高版本中,低版本的Sun JDK 1.6.0包含JAX-WS 2.0),它與JDK內置的HTTP服務器集成。 Spring 同時支持兩個標準Java web服務API。選擇誰主要看運行平臺:在JDK 1.4 / J2EE 1.4上,唯一的選擇是JAX-RPC。在Java EE 5 / Java 6上顯然應該選JAX-WS。運行Java 5的J2EE 1.4環境上,你可以選擇插入一個JAX-WS provider;請查看你的J2EE服務器文檔。 |
除了在Spring Core中支持JAX-RPC and JAX-WS,Spring portfolio也提供了一種特性 Spring Web Services ,一個為優先授權和文檔驅動的web服務所提供的方案 - 非常建議用來創建高級并具有前瞻性的web服務。 XFire 是最后但不是唯一的Spring 內置支持可以讓你將Spring管理的bean暴露為web服務的方式。
Spring為JAX-RPC servlet的端點實現提供了一個方便的基類 - ServletEndpointSupport . 未來暴露我們的 AccountService 我們擴展Spring的 ServletEndpointSupport 類并在這里實現了我們的業務邏輯,通常將調用交給業務層。
/**
* 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中同一個上下文的web應用里運行,以獲得對Spring的訪問能 力。如果使用Axis,把 AxisServlet 定義復制到你的 'web.xml' 中,并且在 'server-config.wsdd' 中 設置端點(或使用發布工具)。參看JPetStore這個例子中 OrderService 是 如何用Axis發布成一個Web服務的。
Spring提供了兩個工廠bean用來創 建Web服務代理, LocalJaxRpcServiceFactoryBean 和 JaxRpcPortProxyFactoryBean 。前者只返回一個JAX-RPC服務類供我們使 用。后者是一個全功能的版本,可以返回一個實現我們業務服務接口的代理。本例中,我們使用后者來為前面段落中暴露的 AccountService 端點創建一個代理。你將看到Spring對Web服務提供了極好的 支持,只需要很少的代碼 - 大多數都是通過類似下面的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 是我們客戶端將使用 的遠程業務接口。 wsdlDocumentUrl 是WSDL文件的URL. Spring需要用它作為啟動點來創建JAX-RPC服務。 namespaceUri 對應.wsdl文件中的targetNamespace。 serviceName 對應.wsdl文件中的服務名。 portName 對應.wsdl文件中的端口號。
現在我們可以很方便的訪問web服務,因為我們有一個可以將它暴露為 RemoteAccountService 接 口的bean工廠。我們可以在Spring中這樣使用:
<bean id="client" class="example.AccountClientImpl">
...
<property name="service" ref="accountWebService"/>
</bean>
從客戶端代碼上看,除了它拋出 RemoteException , 我們可以把這個web服務當成一個普通的類進行訪,。
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 ,因為 Spring將它自動轉換成相應的非受控異常 RemoteException 。這也需要 我們提供一個非RMI的接口?,F在配置文件如下:
<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接口現在使用 portInterface 屬性來定義。我們的客戶端代碼可以 避免處理異常 java.rmi.RemoteException :
public class AccountClientImpl {
private AccountService service;
public void setService(AccountService service) {
this.service = service;
}
public void foo() {
service.insertAccount(...);
}
}
請注意你也可以去掉"portInterface"部分并指定一個普通業務接口作為"serviceInterface"。這樣 JaxRpcPortProxyFactoryBean 將自動切換到JAX-RPC "動態調用接口", 不使用固定端口存根來進行動態調用。這樣做的好處是你甚至不需要使用一個RMI相關的Java接口(比如在非Java的目標web服務中);你只需要一個 匹配的業務接口。查看 JaxRpcPortProxyFactoryBean 的javadoc來 了解運行時實行的細節。
T為了傳遞類似 Account 等復雜對象,我們必須在客戶端注冊bean映射。
在 服務器端通常在 'server-config.wsdd' 中使用Axis進行bean映射注冊。 |
我 們將使用Axis在客戶端注冊bean映射。為此,我們需要通過程序注冊這個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));
}
}
本節中,我們將注冊自己的 javax.rpc.xml.handler.Handler 到Web服務代理,這樣我們可以在SOAP消息被發送前執行定制的代碼。 Handler 是 一個回調接口。 jaxrpc.jar 中有個方便的基類 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;
}
}
我們現在要做的就是把AccountHandler注冊到JAX-RPC服務,這樣它可以在消息被發送前調用 handleRequest(..) 。Spring目前對注冊處理方法還不提供聲明式支持,所以我們必 須使用編程方式。但是Spring中這很容易實現,我們只需覆寫專門為此設計的 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配置文件來使用我們的工廠bean:
<bean id="accountWebService" class="example.AccountHandlerJaxRpcPortProxyFactoryBean">
...
</bean>
Spring為JAX-WS servlet端點實現提供了一個方便的基類 - SpringBeanAutowiringSupport 。 要暴露我們的 AccountService 接口,我們可以擴展Spring的 SpringBeanAutowiringSupport 類并實現我們的業務邏輯,通常把調用交給業務 層。我們將簡單的使用Spring 2.5的 @Autowired 注解來聲明依賴于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設施,我們的 AccountServletEndpoint 類 需要運行在同一個web應用中。在Java EE 5環境中這是默認的情況,它使用JAX-WS servlet端點安裝標準契約。詳情請參閱Java EE 5 web服務教程。
Sun JDK 1.6提供的內置JAX-WS provider 使用內置的HTTP服務器來暴露web服務。Spring的 SimpleJaxWsServiceExporter 類 檢測所有在Spring應用上下文中配置的l @WebService 注解bean,然后通過默認的JAX-WS服務器(JDK 1.6 HTTP服務器)來暴露它們。
在這種場景下,端點實例將被作為 Spring bean來定義和管理。它們將使用JAX-WS來注冊,但其生命周期將一直跟隨Spring應用上下文。這意味著Spring的顯示依賴注入可用于端點實 例。當然通過 @Autowired 來進行注解驅動的注入也可以正常工作。
<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 類,也可能不是。因為這里的端點是 由Spring完全管理的bean。這意味著端點實現可能像下面這樣沒有任何父類定義 - 而且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項目的一部分來開發,它使用了Spring支持來作為JAX-WS Commons項目的一部分。這允許把JAX-WS端點作為Spring管理的bean來定義。這與前面章節討論的單獨模式類似 - 但這次是在Servlet環境中。 注意這在Java EE 5環境中是不可遷移的,建議在沒有EE的web應用環境如Tomcat中嵌入JAX-WS RI。
與標準的暴露基 于servlet的端點方式不同之處在于端點實例的生命周期將被Spring管理。這里在 web.xml 將 只有一個JAX-WS servlet定義。在標準的Java EE 5風格中(如上所示),你將對每個服務端點定義一個servlet,每個服務端點都代理到Spring bean (通過使用 @Autowired ,如上所示)。
關于安裝和使用詳情請查閱 https://jax-ws-commons.dev.java.net/spring/ 。
類似JAX-RPC支持,Spring提供了 2個工廠bean來創建JAX-WS web服務代理,它們是 LocalJaxWsServiceFactoryBean 和 JaxWsPortProxyFactoryBean 。前一個只能返回一個JAX-WS服務對象來讓我 們使用。后面的是可以返回我們業務服務接口的代理實現的完整版本。這個例子中我們使用后者來為 AccountService 端 點再創建一個代理:
<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 是我們客戶端將使用 的遠程業務接口。 wsdlDocumentUrl 是WSDL文件的URL. Spring需要用它作為啟動點來創建JAX-RPC服務。 namespaceUri 對應.wsdl文件中的targetNamespace。 serviceName 對應.wsdl文件中的服務名。 portName 對應.wsdl文件中的端口號。
現在我們可以很方便的訪問web服務,因為我們有一個可以將它暴露為 AccountService 接 口的bean工廠。我們可以在Spring中這樣使用:
<bean id="client" class="example.AccountClientImpl">
...
<property name="service" ref="accountWebService"/>
</bean>
從客戶端代碼上我們可以把這個web服務當成一個普通的類進行訪問:
public class AccountClientImpl {
private AccountService service;
public void setService(AccountService service) {
this.service = service;
}
public void foo() {
service.insertAccount(...);
}
}
注意: 上面被稍微簡化了,因為JAX-WS需要端點接口及實現類來使用 @WebService , @SOAPBinding 等注解。 這意味著你不能簡單的使用普通的Java接口和實現來作為JAX-WS端點,你需要首先對它們進行相應的注解。這些需求詳情請查閱JAX-WS文檔。
XFire是一個Codehaus提供的輕量級SOAP庫。 暴露XFire是通過XFire自帶的context,這個context將和RemoteExporter風格的bean相結合,后者需要被加入到在你 的 WebApplicationContext 中。對于所有讓你來暴露服務的方法,你需 要創建一個 DispatcherServlet 類并有相應的 WebApplicationContext 來封裝你將要暴露的服務:
<servlet>
<servlet-name>xfire</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
你還必須鏈接XFire配置。這是通過增加一個context文件到由 ContextLoaderListener (或者 ContextLoaderServlet ) 加載的 contextConfigLocations 參數中。
<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>
在你加入一個Servlet映射后(映射 /* 到 上面定義的XFire Servlet),你只需要增加一個額外的bean來使用XFire暴露服務。例如,在 '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處理了其他的事情。它檢查你的服務接口并產生一個WSDL文件。這里的部分文檔來自XFire 網站,要了解更多有關XFire Spring的集成請訪問 docs.codehaus.org/display/XFIRE/Spring 。
使 用JMS來作為底層的通信協議透明暴露服務也是可能的。Spring框架中對JMS的遠程支持也很基礎 - 它在 同一線程 和 同一個非事務 Session 上發送和接收,這些吞吐量將非常依賴于實現。
The following interface is used on both the server and the client side.
下 面的接口可同時用在服務端和客戶端。
package com.foo;
public interface CheckingAccountService {
public void cancelAccount(Long accountId);
}
對于上面接口的使用在服務的端簡單實現如下。
package com.foo;
public class SimpleCheckingAccountService implements CheckingAccountService {
public void cancelAccount(Long accountId) {
System.out.println("Cancelling account [" + accountId + "]");
}
}
這個包含JMS設施的bean的配置文件可同時用在客戶端和服務端。
<?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>
在 服務端你只需要使用 JmsInvokerServiceExporter 來暴露服務對象。
<?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"});
}
}
客 戶端僅僅需要創建一個客戶端代理來實現上面的接口( CheckingAccountService )。 根據后面的bean定義創建的結果對象可以被注入到其它客戶端對象中,而這個代理會負責通過JMS將調用轉發到服務端。
<?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 項目提供的支持,它(引用到主頁) “ ... 是一個基于輕量級POJO的遠程核消息代碼庫,它使用并擴展了Spring框架的遠程代碼庫以支持JMS。 ”
對 遠程接口不實現自動探測的主要原因是防止產生太多的遠程調用。目標對象有可能實現的是類似 InitializingBean 或 者 DisposableBean 這樣的內部回調接口,而這些是不希望暴露給調用者的。
提 供一個所有接口都被目標實現的代理通常和本地情況無關。但是當暴露一個遠程服務時,你應該只暴露特定的用于遠程使用的服務接口。除了內部回調接口,目標有 可能實現了多個業務接口,而往往只有一個是用于遠程調用的。出于這些原因,我們 要求 指定這樣的服務接口。
這是在配置方便性和意外暴露內部方法的危險性之間作的平衡。總是指明服務接口并不要花太大代價,并可以讓你控制需暴 露方法從而更加安全。
這 里提到的每種技術都有它的缺點。你在選擇一種技術時,應該仔細考慮你的需要和所暴露的服務及你在遠程訪問時傳送的對象。
當使用RMI時,通過HTTP協議訪問對象是不可能的,除非你用HTTP包裹RMI流。RMI是一種重量級協議,因為它支持整個對象的序列化,當要求網絡 上傳輸復雜數據結構時這是非常重要的。然而,RMI-JRMP只能綁定到Java客戶端:它是一種Java-to-Java的遠程訪問解決方案。
如 果你需要基于HTTP的遠程訪問而且還要求使用Java序列化,Spring的HTTP調用器是一個很好的選擇。它和RMI調用器使用相同的基礎設施,僅 僅使用HTTP作為傳輸方式。注意HTTP調用器不僅只能用在Java-to-Java的遠程訪問,而且在客戶端和服務器端都必須使用 Spring。(Spring為非RMI接口提供的RMI調用器也要求客戶端和服務器端都使用Spring)
在使用服務集群和需要JMS代 理(JMS broker)來處理負載均衡及發現和自動-失敗恢復服務時JMS是很有用的。缺省情況下,在使用JMS遠程服務時使用Java序列化,但是JMS提供者 也可以使用不同的機制例如XStream來讓服務器用其他技術。
最后但不僅限于此,相對于RMI,EJB有一個優點是它支持標準的基于角色 的認證和授權,以及遠程事務傳遞。用RMI調用器或HTTP調用器來支持安全上下文的傳遞是可能的,雖然這不由核心Spring提供:Spring提供了 合適的鉤子來插入第三方或定制的解決方案。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

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