使用 xfire 快速發(fā)布 WebService
在經(jīng)歷過早年 Delphi, C++, Java 等不成熟環(huán)境中開發(fā) WebService 的折磨之后,接觸 ASP.NET 的最大感觸,就是 WebService 的開發(fā)門檻被大大降低了。無需對 SOAP 或基本架構(gòu)有任何了解,僅需簡單的定義幾個 Attribute 即可,所有的 dirty work 都由 ASP.NET 在后臺自動完成。例如:
以下內(nèi)容為程序代碼:
[ WebService(Description="Common Server Variables",Namespace="http://flier.yeah.net/"[img]/images/wink.gif[/img]] public class ServerVariables: WebService {
[ WebMethod(Description="Obtains the Server Computer Name",EnableSession=false)] public string GetMachineName() { return Server.MachineName; } }
| |
而這一巨大的生產(chǎn)力進(jìn)步,現(xiàn)在開始在 Java 領(lǐng)域也可直接享用。例如:
以下內(nèi)容為程序代碼:
/** * Common Server Variables * * @@WebService(name = "ServerVariables", serviceName = "ServerVariables", targetNamespace = "http://flier.yeah.net/"[img]/images/wink.gif[/img] * @@SOAPBinding(style = SOAPBindingAnnotation.STYLE_RPC) */ public interface ServerVariables { /** * @@WebMethod(operationName = "GetMachineName", action="urn:GetMachineName"[img]/images/wink.gif[/img] * @@.return WebResult("machineName"[img]/images/wink.gif[/img] */ string GetMachineName(); }
| |
在項(xiàng)目搭建時完成一次性的配置之后,我們絕大多數(shù)的工作就是定義 WebService 接口,設(shè)定 WebService 的發(fā)布方式。然后就是實(shí)現(xiàn)此接口并放入 spring 中進(jìn)行管理,例如:
以下內(nèi)容為程序代碼:
<bean id="serverVariables" class="net.yeah.flier.ws.impl.ServerVariablesImpl" singleton="true">
| |
這一神奇效果的幕后英雄就是 codehaus 最新發(fā)布的
xfire 1.0 SOAP 框架。
與 Axis 等現(xiàn)有實(shí)現(xiàn)相比,xfire 的最大優(yōu)點(diǎn)就是改用 StAX 基于流的 XML 分析引擎,加上其 SOAP 協(xié)議棧實(shí)現(xiàn)上的精巧設(shè)計(jì),能夠帶來比 Axis 快 2-5 倍的性能提升。具體的性能評測數(shù)據(jù),可以參考
一些第三方評測結(jié)果 。
而在功能上 xfire 也毫不遜色,其 Features & Goals 里面充滿了各種 big words。
以下為引用: * Support for important Web Service standards - SOAP, WSDL, WS-I Basic Profile, WS-Addressing, WS-Security, etc. * High performance SOAP Stack * Pluggable bindings POJOs, XMLBeans, JAXB 1.1, JAXB 2.0, and Castor support * JSR 181 API to configure services via Java 5 and 1.4 (Commons attributes JSR 181 syntax) * Support for many different transports - HTTP, JMS, XMPP, In-JVM, etc. * Embeddable and Intuitive API * Spring, Pico, Plexus, and Loom support. * JBI Support * Client and server stub generation * JAX-WS early access support
|
與 Axis, Glue 以及 ActiveSOAP 的詳細(xì)功能對比,可以參考
Stack Comparison 文檔。
下面先就 xfire 基礎(chǔ)架構(gòu)以及與 spring 集成的基本情況大概做一個簡單介紹,回頭有時間再整一個 xfire 與 axis 實(shí)現(xiàn)架構(gòu)的橫向?qū)Ρ取?br>與 axis 很相似 xfire 在架構(gòu)上也可以大概分為 Service, Transport 和 Invoker 三個層面。
Service 層是 xfire 架構(gòu)的靜態(tài)基礎(chǔ),負(fù)責(zé)完成對服務(wù)的注冊及其管理。核心的 ServiceRegistry 接口完成對服務(wù)自身的生命期管理,如注冊/注銷/獲取等等;而 ServiceFactory 接口則負(fù)責(zé)從具體的 POJO 類型,生成實(shí)現(xiàn) Service 接口的可被管理的服務(wù)代理。
Transport 層則是 xfire 的外部 IO 處理系統(tǒng)。由 TransportManager 接口對具體的 Transport 接口實(shí)現(xiàn)進(jìn)行管理,默認(rèn)提供了基于 pipe 的 LocalTransport 和基于 Http 協(xié)議的 SoapHttpTransport。理論上可以任意進(jìn)行擴(kuò)展,例如 xfire 發(fā)布包中還提供了基于 JMS 和 XMPP 的實(shí)現(xiàn)。
Invoker 則是 xfire 的動態(tài)調(diào)用層,負(fù)責(zé)在 Transport 層接受到請求后,解析內(nèi)容、調(diào)用合適服務(wù)并最終返回 SOAP 封包給調(diào)用者。運(yùn)行時 Invoker 負(fù)責(zé)維系 Service 和 Transport 之間的聯(lián)系。
因此一個服務(wù)的生成和注冊往往類似如下代碼:
以下內(nèi)容為程序代碼:
Service endpoint = serviceFactory.create(clazz); // 根據(jù)具體類型創(chuàng)建服務(wù) xFire.getServiceRegistry().register(endpoint); // 向服務(wù)管理注冊服務(wù) endpoint.setInvoker(new BeanInvoker(bean)); // 設(shè)定服務(wù)調(diào)用模式
| |
最基本的 XFire 接口,實(shí)際上就是 getServiceRegistry() 和 getTransportManager() 的封裝。
xfire 中另一塊核心的思想,就是其靈活而強(qiáng)大的 binding 機(jī)制,負(fù)責(zé)完成 Java 類型與 SOAP 消息的雙向轉(zhuǎn)換。xfire 僅僅內(nèi)建就支持 POJO(Aegis), Castor, JAXB 1.1, JAXB 2.0 和 XMLBeans 多種模式,你可以根據(jù)需求選擇從全自動 POJO 生成,到全手工 xsd 文件定義的不同方式。
而對使用 spring 環(huán)境的開發(fā)者來說,要提供上述這一系列支持,只需要直接將 xfire-spring 工程中的 fire.xml 文件包括到 applicationContext.xml 中,即可直接獲得完整的 xfire 架構(gòu)。
以下內(nèi)容為程序代碼:
<import resource="classpath:/org/codehaus/xfire/spring/xfire.xml"/>
| |
不過相比于通過 xml 文件定義服務(wù)來說,我更傾向于直接用 Annotation 方式在代碼級完成定義。xfire-annotation 提供了對 Java 5 Annotation 和 apache common-attribute 的完整支持。例如上述例子中在 javadoc 里定義的 @@ 標(biāo)簽,就是 用于使用 xdoclet 生成 common-attribute 支持的。
因?yàn)?nbsp;common-attribute 并非編譯器一級提供支持,因此要在編譯之前增加一道處理過程。個人推薦直接使用 maven 進(jìn)行管理,當(dāng)然也可以在 ant 中直接配置 task。maven 1.x 的具體配置方式如下:
以下內(nèi)容為程序代碼:
# # project.properties for maven 1.x # org.apache.commons.attributes.enable=true org.apache.commons.attributes.index.enable=true org.apache.commons.attributes.attributepackages=org.codehaus.xfire.annotations.commons;org.codehaus.xfire.annotations.commons.soap;org.codehaus.xfire.annotations.soap
| |
其中 org.apache.commons.attributes.enable 負(fù)責(zé)啟用 maven 的 common-attribute 支持;org.apache.commons.attributes.index.enable 啟用索引模式,生成時會自動產(chǎn)生一個索引文件 META-INF/attrs.index;org.apache.commons.attributes.attributepackages 則是預(yù)定義可能用到的 annotations 的包名稱,以便直接用 WebService 這樣的簡短名字。
增加了以上配置后,在 maven 編譯時,會自動增加一個 common-attribute 支持文件的編譯過程,并在 target/commons-attributes 下生成類似 ServerVariables$__attributeRepository.java 的文件,以存儲 @@ 標(biāo)簽中指定的 Metadata。在 eclipse 等 IDE 中,我們可以直接把這些目錄添加為源碼目錄。
而在為了使用 common-attribute 定義的元信息,xfire 提供了 Jsr181HandlerMapping 來自動完成服務(wù)映射支持。
以下內(nèi)容為程序代碼:
<bean id="webAnnotations" class="org.codehaus.xfire.annotations.commons.CommonsWebAttributes"/> <bean id="handlerMapping" class="org.codehaus.xfire.spring.remoting.Jsr181HandlerMapping"> <property name="typeMappingRegistry"> <ref bean="xfire.typeMappingRegistry"/> </property> <property name="xfire"> <ref bean="xfire"/> </property> <property name="webAnnotations"> <ref bean="webAnnotations"/> </property> </bean>
| |
因?yàn)?nbsp;xfire 同時需要支持 Java 5 模式和 common-attribute 模式的元信息,所以對元信息的獲取被封裝到 annotations.commons.CommonsWebAttributes 和 annotations.jsr181.Jsr181WebAnnotations 中,以便根據(jù)實(shí)際使用進(jìn)行配置。而 xfire 和 xfire.typeMappingRegistry 則是引用前面提到的 xfire.xml 中定義的基礎(chǔ)接口和類型映射支持。
Jsr181HandlerMapping 是從 spring 提供的 ApplicationObjectSupport 上基礎(chǔ)出來的,它實(shí)現(xiàn)了 ApplicationContextAware 接口,因此會在 bean 定義被加載完成后,調(diào)用其 setApplicationContext 方法,并進(jìn)而調(diào)用其 initApplicationContext 方法完成初始化。
Jsr181HandlerMapping.initApplicationContext 方法中,會遍歷當(dāng)前 ApplicationContext 的所有父容器,調(diào)用 processBeans 方法處理器其 bean 定義。而在 processBeans 中針對每個 bean,檢查其是否為 singleton 的非抽象對象,且定義了 WebService Annotation;如果是則構(gòu)造 bean 并以前面提到的方式進(jìn)行注冊。
值得注意的是,xfire 1.0 乃至最新 CVS 版本,在這塊的實(shí)現(xiàn)都有一個嚴(yán)重 bug,沒有對 abstract bean 進(jìn)行處理。其代碼中直接用 beanFactory.getBean(beanNames[i]) 試圖獲取實(shí)例,如果 bean 是 singleton 的 abstract 模式,則會導(dǎo)致 spring 拋出異常??梢詫⑵錂z測代碼改成如下方式:
以下內(nèi)容為程序代碼:
public class Jsr181HandlerMapping extends AbstractUrlHandlerMapping { private void processBeans(ApplicationContext beanFactory, AnnotationServiceFactory serviceFactory) { String[] beanNames = beanFactory.getBeanDefinitionNames(); ConfigurableApplicationContext ctxt = (ConfigurableApplicationContext) beanFactory;
// Take any bean name or alias that has a web service annotation for (int i = 0; i < beanNames.length; i++) { BeanDefinition def = ctxt.getBeanFactory().getBeanDefinition(beanNames[i]); if (!def.isSingleton() || def.isAbstract()) continue; //... } } }
| |
此 bug 及修復(fù)代碼已提交到 xfire 的 JIRA 上,待進(jìn)一步確認(rèn)。
而在具體對 bean 是否定義服務(wù)的判斷上,xfire 1.0 中的支持實(shí)際上也是很不完整的。象最上面例子中,由 ServerVariables 定義服務(wù)接口,ServerVariablesImpl 中實(shí)現(xiàn)服務(wù)的方式,直接在 xfire 下是無法使用的。關(guān)鍵原因在于 xfire-annotations 工程在定義 WebService 等 annotation 時,沒有指定其可繼承性。
改進(jìn)方法也很簡單,在 org.codehaus.xfire.annotations.commons 包的 WebService, WebMethod, WebResult, WebParam 等 annotation 定義中,類一級增加一個 @@org.apache.commons.attributes.Inheritable() 標(biāo)簽即可。common-attribute 在判斷是否存在某個 annotation 時,會自動將具有 Inheritable 屬性的自定義屬性,繼承到其所有子類。這樣一來就可以很完美的解決通過接口定義服務(wù)的問題。此改進(jìn)也已提交到 xfire 的 JIRA 上,待進(jìn)一步確認(rèn)。
完成了這些基礎(chǔ)性工作后,剩下的任務(wù)就非常簡單了。根據(jù) xfire 的
Servlet Setup 文檔,在 web.xml 中增加相關(guān) servlet 的定義,接管 /service/** 或其它 URL。
以下內(nèi)容為程序代碼:
<servlet> <servlet-name>XFire</servlet-name> <display-name>XFire Servlet</display-name> <servlet-class> org.codehaus.xfire.transport.http.XFireConfigurableServlet </servlet-class> <init-param> <param-name>config</param-name> <param-value>services.xml</param-value> </init-param> </servlet>
<servlet-mapping> <servlet-name>XFire</servlet-name> <url-pattern>/servlet/XFireServlet/*</url-pattern> </servlet-mapping>
<servlet-mapping> <servlet-name>XFire</servlet-name> <url-pattern>/services/*</url-pattern> </servlet-mapping>
| |
然后就可以通過
http://localhost:8080/xfire/services/WeatherService?wsdl 類似的方式訪問 wsdl 或調(diào)用 WebService 了。詳細(xì)的配置請參考 xfire 相關(guān)文檔。
完整的 xfire 支持 bean 配置大致如下:
以下內(nèi)容為程序代碼:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <!-- applicationContext-xfire.xml --> <beans> <import resource="classpath:/org/codehaus/xfire/spring/xfire.xml"/> <bean id="webAnnotations" class="org.codehaus.xfire.annotations.commons.CommonsWebAttributes"/> <bean id="handlerMapping" class="org.codehaus.xfire.spring.remoting.Jsr181HandlerMapping"> <property name="typeMappingRegistry"> <ref bean="xfire.typeMappingRegistry"/> </property> <property name="xfire"> <ref bean="xfire"/> </property> <property name="webAnnotations"> <ref bean="webAnnotations"/> </property> </bean> <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="urlMap"> <map> <entry key="/"> <ref bean="handlerMapping"/> </entry> </map> </property> </bean> </beans>
| |
如果需要使用 abstract bean 的話,可以按前面所說修改 Jsr181HandlerMapping,并重新編譯 xfire-spring 工程;如果需要提供接口定義服務(wù)的支持,可以按前面所說修改 WebService 等,并重現(xiàn)編譯 xfire-annotation 工程。