實戰CXF調用Webxml天氣預報服務

 
http://my.oschina.net/bayer/blog/52490

實戰CXF調用Webxml天氣預報服務

今晚羣裏的kasasis同學問我會不會使用CXF調用

http://www.webxml.com.cn/WebServices/WeatherWebService.asmx?wsdl

這個天氣預報的WebService服務;

我也是初學者, 在嘗試調用的過程中發現不少問題, 也有一些小心得, 希望與大家分享, 更希望大家可以幫我解惑.

一.初探:通過wsdl2java生成調用遠程服務所需要的java類
因爲還沒有成功使用dynamic-clients(http://cxf.apache.org/docs/dynamic-clients.html)的方式動態調用過WebService服務, 所以決定使用CXF提供的wsdl2java來生成調用WebService服務所必須的java類, 進而使用JaxWsServerFactoryBean來調用WebService服務:

遺憾的是, CXF wsdl2java報錯了:
undefined element declaration 's:schema'
at line 85 column 41 of schema 

http://www.webxml.com.cn/WebServices/WeatherWebService.asmx?wsdl

將該wsdl下載下來, 使用MyEclipse 9.0打開並查看, 也提示了這一錯誤:
src-resolve.4.2: Error resolving component 's:schema'.

It was detected that 's:schema' is in namespace 'http://www.w3.org/2001/XMLSchema', but components from this namespace are not referenceable from schema document 'file:///D:/workspace/workspace_j2ee/CXFWeatherTest/src/WeatherWebService.asmx.original.wsdl'.

If this is the incorrect namespace, perhaps the prefix of 's:schema' needs to be changed.

If this is the correct namespace, then an appropriate 'import' tag should be added to 'file:///D:/workspace/workspace_j2ee/CXFWeatherTest/src/WeatherWebService.asmx.original.wsdl'.

wsdl報錯部分內容如下(下邊代碼紅色部分):
Xml代碼  收藏代碼
  1. <s:element name="getSupportDataSetResponse">  
  2.     <s:complexType>  
  3.         <s:sequence>  
  4.             <s:element  
  5.                 minOccurs="0"  
  6.                 maxOccurs="1"  
  7.                 name="getSupportDataSetResult">  
  8.                 <s:complexType>  
  9.                     <s:sequence>  
  10.                         <s:element ref="s:schema" />  
  11.                         <s:any />  
  12.                     </s:sequence>  
  13.                 </s:complexType>  
  14.             </s:element>  
  15.         </s:sequence>  
  16.     </s:complexType>  
  17. </s:element>  


作爲初學者, 曾在風中葉老師的視頻中學習過關於schema及DTD的一些知識, 但還沒修煉到能解決這一錯誤的程度, 也期待各位同學的積極探索並給出答案;

問題催人奮進, 折衷的辦法總是有的:

<s:any/>既然可以代表任何元素, 爲什麼不能代表<s:element ref="xxx"/>元素呢? 於是乎, 憤然刪除<s:element ref="s:schema" />, 勉強算是通過了CXF提供的wsdl驗證工具wsdlvalidator的驗證;

Ok, 繼續之前的步驟, 通過wsdl2java來生成調用遠程WebService服務所需要的java類;

一切順利, 生成的類的目錄結構如下:

 

二.使用JaxWsProxyFactoryBean調用天氣服務
本工程使用的jar包如下圖示:



通過JaxWsProxyFactoryBean調用webService服務的過程相信大家都很熟悉了, 具體代碼如下:
Java代碼  收藏代碼
  1. package cn.com.client;  
  2. import java.util.List;  
  3. import org.apache.cxf.interceptor.LoggingInInterceptor;  
  4. import org.apache.cxf.interceptor.LoggingOutInterceptor;  
  5. import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;  
  6. import cn.com.webxml.ArrayOfString;  
  7. import cn.com.webxml.WeatherWebServiceSoap;  
  8. public class Test {  
  9.     public static void main(String[] args) throws Exception {  
  10.         JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();  
  11.         factory.getInInterceptors().add(new LoggingInInterceptor());  
  12.         factory.getOutInterceptors().add(new LoggingOutInterceptor());  
  13.         factory.setAddress(  
  14.                 "http://www.webxml.com.cn/WebServices/WeatherWebService.asmx");  
  15.         factory.setServiceClass(WeatherWebServiceSoap.class);  
  16.         WeatherWebServiceSoap client =  
  17.                 (WeatherWebServiceSoap) factory.create();  
  18.         ArrayOfString o = client.getWeatherbyCityName("南陽");  
  19.         List<String> strList = o.getString();  
  20.         for (String str : strList) {  
  21.             System.out.println(str);  
  22.         }  
  23.     }  
  24. }  


運行還算順利, 得到的結果如下:

信息: Outbound Message

---------------------------

ID: 1

Address: http://www.webxml.com.cn/WebServices/WeatherWebService.asmx

Encoding: UTF-8

Content-Type: text/xml

Headers: {Accept=[*/*], SOAPAction=["http://WebXml.com.cn/getWeatherbyCityName"]}

Payload:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">

<soap:Body>

<getWeatherbyCityName xmlns="http://WebXml.com.cn/">

<theCityName>南陽</theCityName>

</getWeatherbyCityName>

</soap:Body>

</soap:Envelope>

河南

南陽

57178

57178.jpg

2012-4-5 22:58:11

11℃/24℃

4月6日 晴

無持續風向微風

0.gif

0.gif

今日天氣實況:氣溫:16℃;風向/風力:北風 0級;溼度:53%;空氣質量:較差;紫外線強度:中等

穿衣指數:建議着薄型套裝等春秋過渡裝。年老體弱者宜着套裝。但晝夜溫差較大,注意適當增減衣服。

感冒指數:晝夜溫差較大,較易發生感冒,請適當增減衣服。體質較弱的朋友請注意防護。

運動指數:天氣較好,趕快投身大自然參與戶外運動,盡情感受運動的快樂吧。

洗車指數:適宜洗車,未來持續兩天無雨天氣較好,適合擦洗汽車,藍天白雲、風和日麗將伴您的車子連日潔淨。

晾曬指數:天氣不錯,適宜晾曬。趕緊把久未見陽光的衣物搬出來吸收一下太陽的味道吧!

旅遊指數:天氣晴朗,風和日麗,溫度適宜,是個好天氣哦。這樣的天氣很適宜旅遊,您可以盡情地享受大自然的風光。

路況指數:天氣較好,路面比較乾燥,路況較好。

舒適度指數:溫度適宜,風力不大,您在這樣的天氣條件下,會感到比較清爽和舒適。

空氣污染指數:氣象條件較不利於空氣污染物稀釋、擴散和清除,請適當減少室外活動時間。

紫外線指數:屬中等強度紫外線輻射天氣,外出時建議塗擦SPF高於15、PA+的防曬護膚品,戴帽子、太陽鏡。

11℃/24℃

4月7日 晴

無持續風向微風

0.gif

0.gif

10℃/23℃

4月8日 陰轉多雲

無持續風向微風

2.gif

1.gif

南陽市位於河南省西南部,北靠伏牛山,東扶桐柏山,西依秦嶺,南臨漢江。..................


三.使用JaxWsDynamicClientFactory調用天氣服務
CXF關於Dynamic-clients的介紹文檔見:

http://cxf.apache.org/docs/dynamic-clients.html

很簡潔的一段官方示例代碼:
Java代碼  收藏代碼
  1. JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();  
  2.    
  3. Client client = dcf.createClient("echo.wsdl");  
  4.    
  5. Object[] res = client.invoke("echo""test echo");  
  6.    
  7. System.out.println("Echo response: " + res[0]);  

但具體的調用過程並非一帆風順; 讓人不解的是, 即使調用自己所創建的WebService, 使用動態方式調用也不能成功, 總會提示一個異常:
Exception in thread "main" java.lang.IllegalStateException: Unable to write generated Java files for schemas: null

at org.apache.cxf.endpoint.dynamic.DynamicClientFactory.createClient(DynamicClientFactory.java:351)

at org.apache.cxf.endpoint.dynamic.DynamicClientFactory.createClient(DynamicClientFactory.java:234)

at org.apache.cxf.endpoint.dynamic.DynamicClientFactory.createClient(DynamicClientFactory.java:227)

at org.apache.cxf.endpoint.dynamic.DynamicClientFactory.createClient(DynamicClientFactory.java:182)

at com.client.Test.dynamicStudent(Test.java:30)

at com.client.Test.main(Test.java:38)

Caused by: java.lang.reflect.UndeclaredThrowableException

at $Proxy29.build(Unknown Source)

at org.apache.cxf.endpoint.dynamic.DynamicClientFactory.createClient(DynamicClientFactory.java:349)

... 5 more

Caused by: java.lang.NoSuchMethodException:

com.sun.codemodel.internal.JCodeModel.build(java.lang.Object)

at java.lang.Class.getMethod(Class.java:1605)

at org.apache.cxf.common.util.ReflectionInvokationHandler.invoke(ReflectionInvokationHandler.java:50)

... 7 more
異常信息的最後一個Caused by給了我線索:

java.lang.NoSuchMethodException:

com.sun.codemodel.internal.JCodeModel.build(java.lang.Object)

遍歷工程所用jar文件, 居然沒有找到internal包下的類JCodeModel;

但報的異常是NoSuchMethodException, 而不是ClassNotFoundException, 表明該類是存在的, 可能包路徑發生了變化, 遂STFG(search the fUcking google. 從陳皓老師的博文學到的 - - 囧), 最終發現該類現存在於jaxb-xjc-2.2.4-1.jar中, 包路徑爲com.sun.codemodel, 而不是異常中所提示的com.sun.codemodel.internal;

爲什麼會這樣? 難道是我的jaxb-xjc jar包的版本不對?

遂通過http://jaxb.java.net/, 對比jaxb2.0.1 -- 2.2.5之間的所有版本, 均沒有找到包com.sun.codemodel.internal...

Ok, 讓我們繼續查找jaxb1.x版本的jar包;

幸運的是, 在頁面[url]http://jaxb.java.net/nonav/2.0.1/docs/jaxb-1_0.html [/url], 終於有所發現:

JAXB 2.0 is backwards compatible with JAXB 1.0 - you can deploy your existing 1.0 applications on the 2.0 runtime (provided that you also bundle the jaxb1-impl.jar) and they should run without modification.

查看我的項目的類庫, 剛好缺少了這麼一個jaxb-impl.jar包,,, 加上該包(我使用的是jaxb-impl-2.2.4-1.jar), 再次運行動態調用天氣預報WebService的服務, Congratulations! 這個異常被幹掉了!

但是, 不要高興的太早, 服務並沒有調用成功, 因爲又回到了最開始遇到的問題:

<s:element ref="s:schema" />,

undefined element declaration 's:schema'

at line 85 column 41 of schema

http://www.webxml.com.cn/WebServices/WeatherWebService.asmx?wsdl

具體調用代碼如下:
Java代碼  收藏代碼
  1. package com.client;  
  2. import org.apache.cxf.endpoint.Client;  
  3. import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;  
  4. public class Test {  
  5.     public static void main(String[] args) throws Exception {  
  6.         JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();  
  7.         Client client = dcf.createClient(  
  8.             "http://www.webxml.com.cn/WebServices/WeatherWebService.asmx?wsdl");  
  9.         Object[] res = client.invoke("getSupportProvince");  
  10.         System.out.println("Echo response: " + res[0]);  
  11.     }  
  12. }  


值得欣慰的是, 在補充了jaxb-impl-2.2.4-1.jar之後, 可以通過動態方式成功調用自己創建的WebService服務了, 詳見下節;

四.使用JaxWsDynamicClientFactory動態調用自己創建的WebService服務
服務接口及實現類很簡單:

接口 com.service.StudentService:
Java代碼  收藏代碼
  1. package com.service;  
  2. import javax.jws.WebParam;  
  3. import javax.jws.WebService;  
  4. @WebService  
  5. public interface StudentService {  
  6.     String helloStudent(@WebParam(name="text")String name);  
  7. }  

實現類 com.service.impl.StudentServiceImpl:
Java代碼  收藏代碼
  1. package com.service.impl;  
  2. import javax.jws.WebService;  
  3. import com.service.StudentService;  
  4. @WebService(endpointInterface="com.service.StudentService", targetNamespace="http://service.com/")  
  5. public class StudentServiceImpl implements StudentService{  
  6.     public String helloStudent(String name) {  
  7.         return "hello " + name;  
  8.     }  
  9. }  

server類: com.jettyServer.ServerForJetty:
Java代碼  收藏代碼
  1. package com.jettyServer;  
  2. import javax.xml.ws.Endpoint;  
  3. import com.service.impl.StudentServiceImpl;  
  4. public class ServerForJetty {  
  5.     public static void main(String[] args) throws InterruptedException {  
  6.         StudentServiceImpl implementor = new StudentServiceImpl();  
  7.         String address = "http://localhost:9000/student";  
  8.         Endpoint.publish(address, implementor);  
  9.     }  
  10. }  

client類:com.client.Test
Java代碼  收藏代碼
  1. package com.client;  
  2. import org.apache.cxf.endpoint.Client;  
  3. import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;  
  4. public class Test {  
  5.     public static void main(String[] args) throws Exception {  
  6.         JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();  
  7.         Client client = dcf.createClient("http://localhost:9000/student?wsdl");  
  8.         Object[] res = client.invoke("helloStudent""LeeThinker");  
  9.         System.out.println("Echo response: " + res[0]);  
  10.     }  
  11. }  

先啓動JettyServer, 後訪問http://localhost:9000/student?wsdl, 成功刷新出wsdl, Ok, 服務順利啓動!

再執行Test的main方法, Congratulations! 這次真正使用Dynamic的方式, 在不需要通過wsdl2java生成客戶端java類文件的情況下, 成功調用WebService服務!

至於不能通過這種方式成功調用

http://www.webxml.com.cn/WebServices/WeatherWebService.asmx?wsdl

的問題, 還需要在進一步學習Schema和DTD的知識才能解答;

五.小結:
雖未能通過Dynamic的方式成功調用天氣預報服務, 好歹wsdl2java生成客戶端調用類

的方式是可行的.

六.其他問題:
6.1.@WebService聲明問題
在"四.使用JaxWsDynamicClientFactory調用自己創建的WebService服務"中

自定義服務接口的實現類com.service.impl.StudentServiceImpl的類聲明之前, 僅註釋聲明瞭@WebService, 而沒有後邊的(endpointInterface="com.service.StudentService",

targetNamespace="http://service.com/"), 這時使用動態代理方式調用該服務時, 會發生異常:

Exception in thread "main"

org.apache.cxf.common.i18n.UncheckedException: No operation was  found with the name {http://impl.service.com/}helloStudent.

大概還是因爲WSDL的基礎知識不夠過硬吧, STFG後, 在

http://lost-alien.iteye.com/blog/1175859 找到答案:



在com.service.impl.StudentServiceImpl的類聲明之前, 正確註釋了

@WebService(

endpointInterface="com.service.StudentService",  targetNamespace="http://service.com/")之後,

再使用動態方式調用該服務, 一切順利!

具體原因還有待分析, 認識和理解!

6.2.關於CXF wsdl2java生成的調用遠程服務的客戶端java類文件
在"四.使用JaxWsDynamicClientFactory調用自己創建的WebService服務"中所描述的自定義WebService服務中, 若使用JaxWsServerFactoryBean的調用方式, 那麼首先需要通過wsdl2java生成客戶端必需的java類文件;

將通過自己的wsdl生成的客戶端java類文件的目錄結構&類名稱,

和wsdl2java http://www.webxml.com.cn/WebServices/WeatherWebService.asmx?wsdl

這個wsdl文件後生成的java類文件的目錄結構&類名稱, 對比之後, 發現差距明顯:



對比可以清楚的看到, 自己的wsdl生成的客戶端類文件, 其java類文件名及其混亂, 始終沒能弄懂爲什麼會這樣? 還請各位同學多多指教!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章