?
本文檔使用 php中文網(wǎng)手冊 發(fā)布
Hessian提供一種基于HTTP的二進制遠程協(xié)議。它是由Caucho開發(fā)的,可以在 http://www.caucho.com 找到更多有關(guān)Hessian的信息。
Hessian使用一個特定的Servlet通過HTTP進行通訊。使用Spring在Web MVC中就常用的 DispatcherServlet
原理,可以很容易的配置這樣一個Servlet來暴露你的服務(wù)。首先我們要在你的應(yīng)用里創(chuàng)建一個新的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>/remotingimport 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應(yīng)用里運行,以獲得對Spring的訪問能力。如果使用Axis,把AxisServlet
定義復(fù)制到你的'web.xml'
中,并且在'server-config.wsdd'
中設(shè)置端點(或使用發(fā)布工具)。參看JPetStore這個例子中OrderService
是如何用Axis發(fā)布成一個Web服務(wù)的。
Spring提供了兩個工廠bean用來創(chuàng)建Web服務(wù)代理,LocalJaxRpcServiceFactoryBean
和 JaxRpcPortProxyFactoryBean
。前者只返回一個JAX-RPC服務(wù)類供我們使用。后者是一個全功能的版本,可以返回一個實現(xiàn)我們業(yè)務(wù)服務(wù)接口的代理。本例中,我們使用后者來為前面段落中暴露的AccountService
端點創(chuàng)建一個代理。你將看到Spring對Web服務(wù)提供了極好的支持,只需要很少的代碼 - 大多數(shù)都是通過類似下面的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
是我們客戶端將使用的遠程業(yè)務(wù)接口。
wsdlDocumentUrl
是WSDL文件的URL. Spring需要用它作為啟動點來創(chuàng)建JAX-RPC服務(wù)。
namespaceUri
對應(yīng).wsdl文件中的targetNamespace。
serviceName
對應(yīng).wsdl文件中的服務(wù)名。
portName
對應(yīng).wsdl文件中的端口號。
現(xiàn)在我們可以很方便的訪問web服務(wù),因為我們有一個可以將它暴露為RemoteAccountService
接口的bean工廠。我們可以在Spring中這樣使用:
<bean id="client" class="example.AccountClientImpl"> ... <property name="service" ref="accountWebService"/> </bean>
從客戶端代碼上看,除了它拋出RemoteException
,我們可以把這個web服務(wù)當成一個普通的類進行訪,。
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將它自動轉(zhuǎn)換成相應(yīng)的非受控異常RemoteException
。這也需要我們提供一個非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
屬性來定義。我們的客戶端代碼可以避免處理異常java.rmi.RemoteException
:
public class AccountClientImpl { private AccountService service; public void setService(AccountService service) { this.service = service; } public void foo() { service.insertAccount(...); } }
請注意你也可以去掉"portInterface"部分并指定一個普通業(yè)務(wù)接口作為"serviceInterface"。這樣JaxRpcPortProxyFactoryBean
將自動切換到JAX-RPC "動態(tài)調(diào)用接口", 不使用固定端口存根來進行動態(tài)調(diào)用。這樣做的好處是你甚至不需要使用一個RMI相關(guān)的Java接口(比如在非Java的目標web服務(wù)中);你只需要一個匹配的業(yè)務(wù)接口。查看JaxRpcPortProxyFactoryBean
的javadoc來了解運行時實行的細節(jié)。
T為了傳遞類似Account
等復(fù)雜對象,我們必須在客戶端注冊bean映射。
在服務(wù)器端通常在'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)); } }
本節(jié)中,我們將注冊自己的javax.rpc.xml.handler.Handler
到Web服務(wù)代理,這樣我們可以在SOAP消息被發(fā)送前執(zhí)行定制的代碼。Handler
是一個回調(diào)接口。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; } }
我們現(xiàn)在要做的就是把AccountHandler注冊到JAX-RPC服務(wù),這樣它可以在消息被發(fā)送前調(diào)用 handleRequest(..)
。Spring目前對注冊處理方法還不提供聲明式支持,所以我們必須使用編程方式。但是Spring中這很容易實現(xiàn),我們只需覆寫專門為此設(shè)計的 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端點實現(xiàn)提供了一個方便的基類 - SpringBeanAutowiringSupport
。要暴露我們的AccountService
接口,我們可以擴展Spring的SpringBeanAutowiringSupport
類并實現(xiàn)我們的業(yè)務(wù)邏輯,通常把調(diào)用交給業(yè)務(wù)層。我們將簡單的使用Spring 2.5的@Autowired
注解來聲明依賴于Spring管理的bean。
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
類需要運行在同一個web應(yīng)用中。在Java EE 5環(huán)境中這是默認的情況,它使用JAX-WS servlet端點安裝標準契約。詳情請參閱Java EE 5 web服務(wù)教程。
Sun JDK 1.6提供的內(nèi)置JAX-WS provider 使用內(nèi)置的HTTP服務(wù)器來暴露web服務(wù)。Spring的SimpleJaxWsServiceExporter
類檢測所有在Spring應(yīng)用上下文中配置的l@WebService
注解bean,然后通過默認的JAX-WS服務(wù)器(JDK 1.6 HTTP服務(wù)器)來暴露它們。
在這種場景下,端點實例將被作為Spring bean來定義和管理。它們將使用JAX-WS來注冊,但其生命周期將一直跟隨Spring應(yīng)用上下文。這意味著Spring的顯示依賴注入可用于端點實例。當然通過@Autowired
來進行注解驅(qū)動的注入也可以正常工作。
<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。這意味著端點實現(xiàn)可能像下面這樣沒有任何父類定義 - 而且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項目的一部分來開發(fā),它使用了Spring支持來作為JAX-WS Commons項目的一部分。這允許把JAX-WS端點作為Spring管理的bean來定義。這與前面章節(jié)討論的單獨模式類似 - 但這次是在Servlet環(huán)境中。注意這在Java EE 5環(huán)境中是不可遷移的,建議在沒有EE的web應(yīng)用環(huán)境如Tomcat中嵌入JAX-WS RI。
與標準的暴露基于servlet的端點方式不同之處在于端點實例的生命周期將被Spring管理。這里在web.xml
將只有一個JAX-WS servlet定義。在標準的Java EE 5風格中(如上所示),你將對每個服務(wù)端點定義一個servlet,每個服務(wù)端點都代理到Spring bean (通過使用@Autowired
,如上所示)。
關(guān)于安裝和使用詳情請查閱https://jax-ws-commons.dev.java.net/spring/。
類似JAX-RPC支持,Spring提供了2個工廠bean來創(chuàng)建JAX-WS web服務(wù)代理,它們是LocalJaxWsServiceFactoryBean
和JaxWsPortProxyFactoryBean
。前一個只能返回一個JAX-WS服務(wù)對象來讓我們使用。后面的是可以返回我們業(yè)務(wù)服務(wù)接口的代理實現(xiàn)的完整版本。這個例子中我們使用后者來為AccountService
端點再創(chuàng)建一個代理:
<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
是我們客戶端將使用的遠程業(yè)務(wù)接口。
wsdlDocumentUrl
是WSDL文件的URL. Spring需要用它作為啟動點來創(chuàng)建JAX-RPC服務(wù)。
namespaceUri
對應(yīng).wsdl文件中的targetNamespace。
serviceName
對應(yīng).wsdl文件中的服務(wù)名。
portName
對應(yīng).wsdl文件中的端口號。
現(xiàn)在我們可以很方便的訪問web服務(wù),因為我們有一個可以將它暴露為AccountService
接口的bean工廠。我們可以在Spring中這樣使用:
<bean id="client" class="example.AccountClientImpl"> ... <property name="service" ref="accountWebService"/> </bean>
從客戶端代碼上我們可以把這個web服務(wù)當成一個普通的類進行訪問:
public class AccountClientImpl { private AccountService service; public void setService(AccountService service) { this.service = service; } public void foo() { service.insertAccount(...); } }
注意: 上面被稍微簡化了,因為JAX-WS需要端點接口及實現(xiàn)類來使用@WebService
, @SOAPBinding
等注解。 這意味著你不能簡單的使用普通的Java接口和實現(xiàn)來作為JAX-WS端點,你需要首先對它們進行相應(yīng)的注解。這些需求詳情請查閱JAX-WS文檔。
XFire是一個Codehaus提供的輕量級SOAP庫。暴露XFire是通過XFire自帶的context,這個context將和RemoteExporter風格的bean相結(jié)合,后者需要被加入到在你的WebApplicationContext
中。對于所有讓你來暴露服務(wù)的方法,你需要創(chuàng)建一個DispatcherServlet
類并有相應(yīng)的WebApplicationContext
來封裝你將要暴露的服務(wù):
<servlet> <servlet-name>xfire</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> </servlet>
你還必須鏈接XFire配置。這是通過增加一個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>
在你加入一個Servlet映射后(映射 缺省情況下,Spring EJB支持類的基類在其生命周期中將創(chuàng)建并加載一個Spring IoC容器供EJB使用(比如像前面獲得POJO服務(wù)對象的代碼)。加載的工作是通過一個策略對象完成的,它是 如JavaDoc中所述,有狀態(tài)Session Bean在其生命周期中將會被鈍化并重新激活,由于(一般情況下)使用了一個不可串行化的容器實例,不可以被EJB容器保存,
所以還需要手動在 有些情況下,要載入ApplicationContext以使用EJB組件, 然后需要創(chuàng)建一個名為 上例中,常量
protected void onEjbCreate() throws CreateException {
myComp = (MyComponent) getBeanFactory().getBean(
ServicesConstants.CONTEXT_MYCOMP_ID);
}
// for business method, delegate to POJO service impl.
public String myFacadeMethod(...) {
return myComp.myMethod(...);
}
...
}
BeanFactoryLocator
的子類。
默認情況下,實際使用的BeanFactoryLocator
的實現(xiàn)類是ContextJndiBeanFactoryLocator
,它根據(jù)一個被指定為JNDI環(huán)境變量的資源位置來創(chuàng)建一個ApplicationContext對象(對于EJB類,路徑是
java:comp/env/ejb/BeanFactoryPath
)。如果需要改變BeanFactory或ApplicationContext的載入策略,我們可以在
setSessionContext()
方法調(diào)用或在具體EJB子類的構(gòu)造函數(shù)中調(diào)用setBeanFactoryLocator()
方法來覆蓋默認使用的
BeanFactoryLocator
實現(xiàn)類。具體細節(jié)請參考JavaDoc。
ejbPassivate
和ejbActivate
這兩個方法中分別調(diào)用unloadBeanFactory()
和loadBeanFactory
,
才能在鈍化或激活的時候卸載或載入。ContextJndiBeanFactoryLocator
的默認實現(xiàn)基本上足夠了,
不過,當ApplicationContext
需要載入多個bean,或這些bean初始化所需的時間或內(nèi)存
很多的時候(例如Hibernate的SessionFactory
的初始化),就有可能出問題,因為
每個EJB組件都有自己的副本。這種情況下,用戶會想重載ContextJndiBeanFactoryLocator
的默認實現(xiàn),并使用其它
BeanFactoryLocator
的變體,例如ContextSingletonBeanFactoryLocator
,他們可以載入并在多個EJB或者其客戶端間共享一個容器。這樣做相當簡單,只需要給EJB添加類似于如下的代碼:
public void setSessionContext(SessionContext sessionContext) {
super.setSessionContext(sessionContext);
setBeanFactoryLocator(ContextSingletonBeanFactoryLocator.getInstance());
setBeanFactoryLocatorKey(ServicesConstants.PRIMARY_CONTEXT_ID);
}
beanRefContext.xml
的bean定義文件。這個文件定義了EJB中所有可能用到的bean工廠(通常以應(yīng)用上下文的形式)。許多情況下,這個文件只包括一個bean的定義,如下所示(文件businessApplicationContext.xml
包括了所有業(yè)務(wù)服務(wù)POJO的bean定義):<beans>
<bean id="businessBeanFactory" class="org.springframework.context.support.ClassPathXmlApplicationContext">
<constructor-arg value="businessApplicationContext.xml" />
</bean>
</beans>
ServicesConstants.PRIMARY_CONTEXT_ID
定義如下: public static final String ServicesConstants.PRIMARY_CONTEXT_ID = "businessBeanFactory";
BeanFactoryLocator
和類ContextSingletonBeanFactoryLocator
的更多使用信息請分別查看他們各自的Javadoc文檔。
對EJB3 Session bean和Message-Driven Bean來說, Spring在EJB組件類
org.springframework.ejb.interceptor.SpringBeanAutowiringInterceptor
中提供了實用的攔截器來解析Spring2.5的注解@Autowired
。
這個攔截器的使用有兩種方式,可以在EJB組件類里使用@Interceptors
注解,也可以在EJB部署描述文件中使用XML元素interceptor-binding
。
@Stateless @Interceptors(SpringBeanAutowiringInterceptor.class) public class MyFacadeEJB implements MyFacadeLocal { // automatically injected with a matching Spring bean @Autowired private MyComponent myComp; // for business method, delegate to POJO service impl. public String myFacadeMethod(...) { return myComp.myMethod(...); } ... }
SpringBeanAutowiringInterceptor
默認情況下是從ContextSingletonBeanFactoryLocator
獲得目標bean的,后者定義在beanRefContext.xml
文件中。通常情況下,最好使用單獨的上下文定義,并且根據(jù)類型而不是名稱來獲得。然而,如果你需要在多個上下文定義中切換,那么就需要一個特定的定位鍵。這個定位鍵(例如定義在beanRefContext.xml
中的上下文名稱)可以通過以下兩種方式來明確的指定。一種方式是在定制的SpringBeanAutowiringInterceptor
子類中重寫getBeanFactoryLocatorKey
方法。
另一種方式是重寫SpringBeanAutowiringInterceptor
的
getBeanFactory
方法,例如從定制支持類中獲得一個共享的ApplicationContext
。