實戰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報錯部分內容如下(下邊代碼紅色部分):
- <s:element name="getSupportDataSetResponse">
- <s:complexType>
- <s:sequence>
- <s:element
- minOccurs="0"
- maxOccurs="1"
- name="getSupportDataSetResult">
- <s:complexType>
- <s:sequence>
- <s:element ref="s:schema" />
- <s:any />
- </s:sequence>
- </s:complexType>
- </s:element>
- </s:sequence>
- </s:complexType>
- </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服務的過程相信大家都很熟悉了, 具體代碼如下:
- package cn.com.client;
- import java.util.List;
- import org.apache.cxf.interceptor.LoggingInInterceptor;
- import org.apache.cxf.interceptor.LoggingOutInterceptor;
- import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
- import cn.com.webxml.ArrayOfString;
- import cn.com.webxml.WeatherWebServiceSoap;
- public class Test {
- public static void main(String[] args) throws Exception {
- JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
- factory.getInInterceptors().add(new LoggingInInterceptor());
- factory.getOutInterceptors().add(new LoggingOutInterceptor());
- factory.setAddress(
- "http://www.webxml.com.cn/WebServices/WeatherWebService.asmx");
- factory.setServiceClass(WeatherWebServiceSoap.class);
- WeatherWebServiceSoap client =
- (WeatherWebServiceSoap) factory.create();
- ArrayOfString o = client.getWeatherbyCityName("南陽");
- List<String> strList = o.getString();
- for (String str : strList) {
- System.out.println(str);
- }
- }
- }
運行還算順利, 得到的結果如下:
信息: 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
很簡潔的一段官方示例代碼:
- JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
- Client client = dcf.createClient("echo.wsdl");
- Object[] res = client.invoke("echo", "test echo");
- 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
具體調用代碼如下:
- package com.client;
- import org.apache.cxf.endpoint.Client;
- import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;
- public class Test {
- public static void main(String[] args) throws Exception {
- JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
- Client client = dcf.createClient(
- "http://www.webxml.com.cn/WebServices/WeatherWebService.asmx?wsdl");
- Object[] res = client.invoke("getSupportProvince");
- System.out.println("Echo response: " + res[0]);
- }
- }
值得欣慰的是, 在補充了jaxb-impl-2.2.4-1.jar之後, 可以通過動態方式成功調用自己創建的WebService服務了, 詳見下節;
四.使用JaxWsDynamicClientFactory動態調用自己創建的WebService服務
服務接口及實現類很簡單:
接口 com.service.StudentService:
- package com.service;
- import javax.jws.WebParam;
- import javax.jws.WebService;
- @WebService
- public interface StudentService {
- String helloStudent(@WebParam(name="text")String name);
- }
實現類 com.service.impl.StudentServiceImpl:
- package com.service.impl;
- import javax.jws.WebService;
- import com.service.StudentService;
- @WebService(endpointInterface="com.service.StudentService", targetNamespace="http://service.com/")
- public class StudentServiceImpl implements StudentService{
- public String helloStudent(String name) {
- return "hello " + name;
- }
- }
server類: com.jettyServer.ServerForJetty:
- package com.jettyServer;
- import javax.xml.ws.Endpoint;
- import com.service.impl.StudentServiceImpl;
- public class ServerForJetty {
- public static void main(String[] args) throws InterruptedException {
- StudentServiceImpl implementor = new StudentServiceImpl();
- String address = "http://localhost:9000/student";
- Endpoint.publish(address, implementor);
- }
- }
client類:com.client.Test
- package com.client;
- import org.apache.cxf.endpoint.Client;
- import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;
- public class Test {
- public static void main(String[] args) throws Exception {
- JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
- Client client = dcf.createClient("http://localhost:9000/student?wsdl");
- Object[] res = client.invoke("helloStudent", "LeeThinker");
- System.out.println("Echo response: " + res[0]);
- }
- }
先啓動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類文件名及其混亂, 始終沒能弄懂爲什麼會這樣? 還請各位同學多多指教!