http://tonylian.iteye.com/blog/680916
[轉]用Sping發佈WebService
我的需求是這樣的:系統已經成型,僅僅通過基礎架構的很小變化,達到將服務開放爲WebService的目的。
1、不用或幾乎不用修改已有代碼
2、簡單的xml配置
3、通用性強,各種開發環境皆可訪問
4、參數與返回型都要支持DTO(POJO)
以上四點需求,看起來很簡單,可想而知各種Java的WebService方案應該都能夠滿足。但是,在實際藉助網絡搜索答案的時候卻發現,能搜到的帖子,基本都是相互轉帖的,所有的例子幾乎一樣,就那麼2、3篇。
雖然看了幾種實現Webservice方法的比較的帖子,筆者認爲與Spring整合的話,CXF比較好,而且似乎有後來居上,掀翻Axis/Axis2的趨勢。但我還是分別實踐了一下Axis/Axis2/CXF([size=x-small;]Celtix、XFire只看了看帖子,既然已經合併爲CXF了,就沒有再去實踐[/size]),也親身體驗了一把。最終還是初步選定了CXF,下面談一點體會:
1、其實Axis和Axis2都是可以與Spring很簡單的整合的,網上的帖子說不能,這一點我有些質疑。
2、速度我沒有比較,網上說CXF比Axis1快4-5倍
3、既然有了Axis2,而且是重新設計的,完全替代Axis,所以Axis就沒有仔細琢磨,僅僅調通了一次,就不多說了
4、Axis2的略勢(僅僅從開發方便性來說,性能、通用性等尚未關注)是:
4.1、配置文件方式複雜,要求在WEB-INF下建立servers目錄,裏面是每個WebService各一個目錄,各WebService的目錄裏再有META-INF目錄,最後在裏面放services.xml。一個WebService就要一個xml配置文件不說,還要複雜的目錄結構
4.2、jar包超多。Axis1都打到一個jar包裏了,足足15M,Axis2爲了讓jar包變小,分工明晰,分解了jar包,但是也太多了,足足62個,這還不算axiom的。爲他瘦身就要花一番功夫(我不喜歡把所有的jar都一股腦的扔到lib裏,能少一個就少一個)
4.3、用.Net做Client訪問返回的DTO中的DTO類型出現錯誤。比如,返回一個department(部門),此對象除id、name等String型屬性外,還有List employees屬性。我的服務返回的department.employees明明只有一個對象,而Client端得到的卻顯示List有3個內容,因爲Employee有3個屬性(id、name、age),分別爲string、string、int
5、CXF的略勢(僅僅是我認爲的):
5.1、CXF要求註釋語法,而修改現有代碼是我不願意看到的
5.2、作爲妥協,僅僅在接口中使用最簡單的註釋@WebService,其他非必須的地方都不寫註釋。導致生成的WebService的名稱、命名空間、DTO的類型都由CXF自行命名,尤其是DTO的類名,都是小寫開頭的,非常彆扭。
5.3、從暴露出來的wsdl看,只有SOAP1.1協議,而Axis2還有SOAP1.2和普通Http
以上就是我初步實踐的比較結論,綜合看,CXF還是有優勢的。尤其是Axis2的4.2是個致命問題,不知道是不是我沒有調試好,但似乎也看到一篇帖子說Axis2在DTO(自定義類型)的傳遞上不如CXF。
5.1、5.2問題習慣就好了,5.3也不是大問題,用1.1反而是穩妥的,兼容性更好的。
以下就詳細分析一下CXF,例子代碼我就儘量不大段的貼了,搜索一下有的是。
接口定義:
- @WebService(name = "UserService",targetNamespace = Constants.WS_NAMESPACE)
- public interface UserWebService {
- @WebResult(name = "user")
- public UserDTO getUser(@WebParam(name = "userId") Integer userId) throws FaultException;
- }
@WebService(name = "UserService",targetNamespace = Constants.WS_NAMESPACE)
public interface UserWebService {
@WebResult(name = "user")
public UserDTO getUser(@WebParam(name = "userId") Integer userId) throws FaultException;
}
- @WebService 必須,(name="UserService")可選,配置Service的名稱,默認爲類名。targetNamespace可選,默認爲http:// package的倒序,可在一個自己的Constants裏定義統一的namespace.
- 默認接口的所有方法均輸出爲WebService。
- @WebResult 可選,配置方法的返回值在WSDL的名稱,CXF默認爲result。
- @WebParam 可選,配置參數在WSDL的名稱,CX比較笨,不會反射,默認爲arg0,arg1....
- @WebMethod 可選,有一個方法有此註釋,則其他無註釋的方法非WebService。
接口實現:
- @WebService(endpointInterface = "xxx.xxx.service.UserWebService",targetNamespace = Constants.WS_NAMESPACE)
- public class UserWebServiceImpl {
- //...
- }
@WebService(endpointInterface = "xxx.xxx.service.UserWebService",targetNamespace = Constants.WS_NAMESPACE)
public class UserWebServiceImpl {
//...
}
@WebService(endpointInterface="") 可選,指定實現的接口。接口是對外的,必須通過註釋來聲明,而Impl是內部的,聲明是可選的。
DTO:
- @XmlType(name = "User")
- public class UserDTO {
- //屬性及setter/getter方法...
- }
@XmlType(name = "User")
public class UserDTO {
//屬性及setter/getter方法...
}
- @XmlType(name = "User") 可選的
- JAXB的智能化較高,基本上不需要手工映射。
- 默認的@XmlAccessorType(XmlAccessType.PUBLIC_MEMBER )根據公共getter/setter與公共屬性反射(XmlAccessType.PROPERTY根據getter/setter反射,XmlAccessType.FIELD 根據成員變量反射)
- 如果有需要註釋@XMLElement,@XMLAttribute,需配合XmlAccessorType定義,默認的PUBLIC_MEMBER,需要在getter/setter上定義,如果要寫在成員變量上定義,則XmlAccessType改爲FIELD。
- @XmlType(name = "User") 指定WSDL上的類型名稱,否則CXF奇怪,DTO的類名是小寫開頭的。
- @XmlTransient 可以註釋某個字段,取消該字段的反射。
總結一下:
1.統一:每一個接口函數,每一個DTO類都需要定義NameSpace,否則CXF會以類的pacage名倒序作爲namespace,大幅提高引發命名空間的複雜度。最好在Constants類裏統一定義。
2.接口: CXF比較笨,所以還需要註釋每個方法的參數名。否則會以arg0,arg1命名,用SoapUI 等工具看SOAP包時比較痛苦。(Axis2也是arg0,arg1)
在ServiceImpl中,最好命名serviceName,否則默認會議類名+Service來命名,如UserManagerServiceImplService,比較難看
3.DTO:用@XmlType定義 DTO名,否則名稱默認小寫,在.Net下生成的代碼,類名都是小寫的,超奇怪。
最後要着重指出一點,在網上能搜索到的N多帖子都是如出一轍,但是估計第一個人犯了錯誤,而後來者都是不負責任的粘貼!!估計他們都是沒有經過自己測試的!太誤人子弟了!!
這就是 Server端 作爲Spring和CXF橋樑的那個xml配置文件中的寫法,引用一段網上的帖子:
在spring-cxf.xml配置發佈的web service
- <!-- 上半段省略 -->
- <bean id="hello" class="test.HelloWorldImpl" />
- <jaxws:endpoint id="helloWorld" implementor="#hello"
- address="/HelloWorld" />
- </beans>
- 注意: id:指在spring配置的bean的ID.
Implementor:指明具體的實現類.
Address:指明這個web service的相對地址
請注意這裏一共犯了兩個錯誤:
1、id,作爲標籤的id,它當然是這個endpoint的id,不可能是“指在spring配置的bean的ID”
2、implementor用#引用了上面聲明的一個bean,其實implementor=的引用才應該指向在spring配置的bean的ID,spring-cxf.xml中不用也不該再定義一個bean了
正確的寫法是:
假設Spring的配置文件applicationContext.xml中定義了一個bean
- <bean id="helloWorld" class="test.HelloWorldImpl">
- <property name="userDao">
- <ref bean="userDao" /> <!-- 這裏注入一個DAO -->
- </property>
- </bean>
spring-cxf.xml應該這樣寫:
- <jaxws:endpoint id="helloWorldEndpoint"
- implementor="#helloWorld" <!-- 這裏是與spring定義的bean呼應的 -->
- address="/HelloWorldService" />
- t;/beans>
試想,如果在implementor參數裏指定具體實現類,那spring注入時的“接口指向用具體實現類”還有什麼意義呢?
最初的作者僅僅調試了一個HelloWorld就草草落筆,他們並沒有仔細觀察,當客戶端來訪時,服務器是新實例化了一個服務呢?還是從spring已經注入好的bean中get一個呢?
顯然我們要的是後者的效果,而錯誤的寫法確實前者的效果,雖然HelloWorld能通,但是稍複雜一點,比如sping的bean中又注入了其他bean,那麼一下就能發現錯誤了,因爲錯誤的寫法是另外實例化了一個bean,由於他是在錯誤的spring-cxf.xml中定義的,自然沒有寫property。
所以寫博客、發帖子,都應該本着負責和科學的態度,不能人云亦云,尤其是技術貼,自己沒有驗證的,請不要Ctrl-C / Ctrl-V !
----------------------一條4年半後的分割線-----------------------------------------------------------------------------
今天,大約此貼發帖後4年半的一天,看到一位網友的回覆,我又看了一遍這篇文章。
於是想補充一點,後來經過進一步實踐,我早已達到了帖子首部提出的4點要求,尤其是第一點,
已經可以做到絲毫不修改原有代碼既可以發佈WebService。
就是說不需要使用註釋的方式了修改代碼了。
只需要在配置文件中加入
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:simple="http://cxf.apache.org/simple"
- xmlns:soap="http://cxf.apache.org/bindings/soap"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans.xsd
- http://cxf.apache.org/core
- http://cxf.apache.org/schemas/core.xsd
- http://cxf.apache.org/simple
- http://cxf.apache.org/schemas/simple.xsd">
- <import resource="classpath:META-INF/cxf/cxf.xml" />
- <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
- <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
- <bean id="aegisBean" class="org.apache.cxf.aegis.databinding.AegisDatabinding" />
- <simple:server id="webServiceUserAccountService" serviceClass="com.xxxx.service.UserAccountService"
- address="/UserAccountService">
- <simple:serviceBean>
- <ref bean="userAccountService" />
- </simple:serviceBean>
- <simple:dataBinding>
- <ref bean="aegisBean" />
- </simple:dataBinding>
- </simple:server>
- </beans>
這樣就把原來一個普通的【userAccountService】開放爲WebService了。
每增加一個WebService,在這個配置文件裏增加一段<simple></simple>就可以了。
記着在applicationContext.xml中引用這個xml哦。
最後別忘了在web.xml中加上CXF的監聽
- <servlet>
- <servlet-name>CXFServlet</servlet-name>
- <servlet-class>
- org.apache.cxf.transport.servlet.CXFServlet
- </servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>CXFServlet</servlet-name>
- <url-pattern>/webservice/*</url-pattern>
- </servlet-mapping>
----------------------一條又過了半年多的分割線-----------------------------------------------------------------------------
今天又看到了這個帖子,這半年間,我又優化了一下,這個WebService的發佈方法。
兩條分割線之間的方法其實已經很好了,最大的優點就是配置簡單,且對代碼零污染。
但是,我也發現它的一點問題,就是被開放爲WebService的原Service的所有方法都會被開放出來。
那位說了,不就是爲了都開放出來嗎?不想開放的方法改到另外一個服務中不就行了。
其實,問題可能不出在業務方法上,而是我們的Service的父類中的一些系統級方法,
比如 commit, rollback 之類的。
這些方法開放出去,顯得那麼不讓人放心。
那麼我們就需要改一下配置文件,把<simple:Server>標籤改爲<jaxws:endpoint>標籤。
這樣做後,需要一點點的對代碼的污染,就是在想要開放爲WebService的Service的接口中,
(主意只是接口中,實現類不需要)
給接口定義加一個 @WebService 的註解
(如果業務方法也有一些想開放的,一些不想開放的,又不願意應爲開不開放而破壞原有的
邏輯把這些方法重新分到不同的Service中,那麼還可以使用方法級的註解)
這樣,因爲父類中定義的方法(commit, rollback等)沒有註解,就不會開放到WebService中了。
- <jaxws:endpoint id="webUserAccountService" implementorClass="cn.xxxxx.service.UserAccountMasterDataService"
- address="/UserAccountService">
- <jaxws:implementor>
- <ref bean="userAccountService" />
- </jaxws:implementor>
- <jaxws:dataBinding>
- <ref bean="aegisBean" />
- </jaxws:dataBinding>
- </jaxws:endpoint>
- import javax.jws.WebService;
- @WebService
- public interface UserAccountService extends BaseService{
import javax.jws.WebService;
@WebService
public interface UserAccountService extends BaseService{