[轉]用Sping發佈WebService

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,例子代碼我就儘量不大段的貼了,搜索一下有的是。


接口定義:

Java代碼 複製代碼 收藏代碼
  1. @WebService(name = "UserService",targetNamespace = Constants.WS_NAMESPACE)   
  2. public interface UserWebService {   
  3.     @WebResult(name = "user")   
  4.     public UserDTO getUser(@WebParam(name = "userId") Integer userId) throws FaultException;   
  5. }   
@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。



接口實現:

Java代碼 複製代碼 收藏代碼
  1. @WebService(endpointInterface = "xxx.xxx.service.UserWebService",targetNamespace = Constants.WS_NAMESPACE)   
  2. public class UserWebServiceImpl {  
  3.     //...  
  4. }  
@WebService(endpointInterface = "xxx.xxx.service.UserWebService",targetNamespace = Constants.WS_NAMESPACE) 
public class UserWebServiceImpl {
    //...
}

 

@WebService(endpointInterface="")  可選,指定實現的接口。接口是對外的,必須通過註釋來聲明,而Impl是內部的,聲明是可選的。


DTO:

Java代碼 複製代碼 收藏代碼
  1. @XmlType(name = "User")   
  2. public class UserDTO {   
  3.     //屬性及setter/getter方法...   
  4. }   
@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

Xml代碼 複製代碼 收藏代碼
  1. <!-- 上半段省略 -->  
  2.     <bean id="hello" class="test.HelloWorldImpl" />    
  3.     <jaxws:endpoint id="helloWorld" implementor="#hello"    
  4.         address="/HelloWorld" />    
  5. </beans>  
  6. 注意: 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

Xml代碼 複製代碼 收藏代碼
  1. <bean id="helloWorld" class="test.HelloWorldImpl">  
  2.         <property name="userDao">  
  3.             <ref bean="userDao" />  <!-- 這裏注入一個DAO -->  
  4.         </property>  
  5. </bean>  

 

spring-cxf.xml應該這樣寫:

Xml代碼 複製代碼 收藏代碼
  1. <jaxws:endpoint id="helloWorldEndpoint"  
  2.     implementor="#helloWorld"  <!-- 這裏是與spring定義的bean呼應的 -->  
  3.     address="/HelloWorldService" />      
  4. 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。

就是說不需要使用註釋的方式了修改代碼了。

 

只需要在配置文件中加入

可以叫 applicationcontext-webservice.xml代碼 複製代碼 收藏代碼
  1. <?xml version="1.0" encoding="UTF-8"?>     
  2. <beans xmlns="http://www.springframework.org/schema/beans"    
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    
  4.     xmlns:simple="http://cxf.apache.org/simple"  
  5.     xmlns:soap="http://cxf.apache.org/bindings/soap"  
  6.     xsi:schemaLocation="http://www.springframework.org/schema/beans   
  7.         http://www.springframework.org/schema/beans/spring-beans.xsd  
  8.         http://cxf.apache.org/core  
  9.         http://cxf.apache.org/schemas/core.xsd    
  10.         http://cxf.apache.org/simple      
  11.         http://cxf.apache.org/schemas/simple.xsd">    
  12.     <import resource="classpath:META-INF/cxf/cxf.xml" />      
  13.     <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />      
  14.     <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />    
  15.   
  16.     <bean id="aegisBean" class="org.apache.cxf.aegis.databinding.AegisDatabinding" />    
  17.     
  18.     <simple:server id="webServiceUserAccountService" serviceClass="com.xxxx.service.UserAccountService"    
  19.         address="/UserAccountService">    
  20.         <simple:serviceBean>    
  21.             <ref bean="userAccountService" />    
  22.         </simple:serviceBean>    
  23.         <simple:dataBinding>    
  24.             <ref bean="aegisBean" />    
  25.         </simple:dataBinding>    
  26.     </simple:server>    
  27.   
  28. </beans>   

 這樣就把原來一個普通的【userAccountService】開放爲WebService了。

每增加一個WebService,在這個配置文件裏增加一段<simple></simple>就可以了。

記着在applicationContext.xml中引用這個xml哦。

 

最後別忘了在web.xml中加上CXF的監聽

Xml代碼 複製代碼 收藏代碼
  1. <servlet>    
  2.     <servlet-name>CXFServlet</servlet-name>    
  3.     <servlet-class>    
  4.         org.apache.cxf.transport.servlet.CXFServlet    
  5.     </servlet-class>    
  6. </servlet>    
  7. <servlet-mapping>    
  8.     <servlet-name>CXFServlet</servlet-name>    
  9.     <url-pattern>/webservice/*</url-pattern>    
  10. </servlet-mapping>  

 

 ----------------------一條又過了半年多的分割線-----------------------------------------------------------------------------

 

今天又看到了這個帖子,這半年間,我又優化了一下,這個WebService的發佈方法。

 

兩條分割線之間的方法其實已經很好了,最大的優點就是配置簡單,且對代碼零污染。

 

但是,我也發現它的一點問題,就是被開放爲WebService的原Service的所有方法都會被開放出來。

那位說了,不就是爲了都開放出來嗎?不想開放的方法改到另外一個服務中不就行了。

其實,問題可能不出在業務方法上,而是我們的Service的父類中的一些系統級方法,

比如 commit, rollback 之類的。

這些方法開放出去,顯得那麼不讓人放心。

 

那麼我們就需要改一下配置文件,<simple:Server>標籤改爲<jaxws:endpoint>標籤。

這樣做後,需要一點點的對代碼的污染,就是在想要開放爲WebService的Service的接口中,

(主意只是接口中,實現類不需要)

給接口定義加一個 @WebService 的註解

(如果業務方法也有一些想開放的,一些不想開放的,又不願意應爲開不開放而破壞原有的

邏輯把這些方法重新分到不同的Service中,那麼還可以使用方法級的註解)

 

這樣,因爲父類中定義的方法(commit, rollback等)沒有註解,就不會開放到WebService中了。

 

 

Xml代碼 複製代碼 收藏代碼
  1. <jaxws:endpoint id="webUserAccountService" implementorClass="cn.xxxxx.service.UserAccountMasterDataService"    
  2.        address="/UserAccountService">    
  3.     <jaxws:implementor>    
  4.         <ref bean="userAccountService" />    
  5.     </jaxws:implementor>    
  6.     <jaxws:dataBinding>    
  7.         <ref bean="aegisBean" />    
  8.     </jaxws:dataBinding>    
  9. </jaxws:endpoint>  
 

 

Java代碼 複製代碼 收藏代碼
  1. import javax.jws.WebService;  
  2. @WebService  
  3. public interface UserAccountService extends BaseService{  
import javax.jws.WebService;
@WebService
public interface UserAccountService extends BaseService{
 
這樣,我們就保護了BaseService中聲明的方法不被一同開放爲WebService了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章