實戰CXF呼叫Webxml天氣預報服務

不淨之心發表於2013-04-08
[url]http://my.oschina.net/bayer/blog/52490[/url]

實戰CXF呼叫Webxml天氣預報服務

今晚群裡的kasasis同學問我會不會使用CXF呼叫

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

這個天氣預報的WebService服務;

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

[color=red][b]一.初探:通過wsdl2java生成呼叫遠端服務所需要的java類[/b][/color]
因為還沒有成功使用dynamic-clients(http://cxf.apache.org/docs/dynamic-clients.html)的方式動態呼叫過WebService服務, 所以決定使用CXF提供的wsdl2java來生成呼叫WebService服務所必須的java類, 進而使用JaxWsServerFactoryBean來呼叫WebService服務:
[img]http://static.oschina.net/uploads/space/2012/0406/005536_rxYD_184760.png[/img]
遺憾的是, CXF wsdl2java報錯了:
[i]undefined element declaration 's:schema'
at line 85 column 41 of schema [/i]
[url]http://www.webxml.com.cn/WebServices/WeatherWebService.asmx?wsdl[/url]

將該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類;

一切順利, 生成的類的目錄結構如下:
[img]http://static.oschina.net/uploads/space/2012/0406/005423_iXEa_184760.png[/img]


[color=red][b]二.使用JaxWsProxyFactoryBean呼叫天氣服務[/b][/color]
本工程使用的jar包如下圖示:
[img]http://static.oschina.net/uploads/space/2012/0406/005630_GAWN_184760.png[/img]


通過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

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

[color=darkblue]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

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

[color=red][b]三.使用JaxWsDynamicClientFactory呼叫天氣服務[/b][/color]
CXF關於Dynamic-clients的介紹文件見:

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

很簡潔的一段官方示例程式碼:
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

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

具體呼叫程式碼如下:
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服務了, 詳見下節;

[color=red][b]四.使用JaxWsDynamicClientFactory動態呼叫自己建立的WebService服務[/b][/color]
服務介面及實現類很簡單:

介面 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服務!

至於不能通過這種方式成功呼叫

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

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

[color=red][b]五.小結:[/b][/color]
雖未能通過Dynamic的方式成功呼叫天氣預報服務, 好歹wsdl2java生成客戶端呼叫類

的方式是可行的.

[color=red][b]六.其他問題:[/b][/color]
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 找到答案:
[img]http://static.oschina.net/uploads/space/2012/0406/005811_5dLz_184760.png[/img]


在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類檔案的目錄結構&類名稱, 對比之後, 發現差距明顯:
[img]http://static.oschina.net/uploads/space/2012/0406/005912_lAAn_184760.png[/img]


對比可以清楚的看到, 自己的wsdl生成的客戶端類檔案, 其java類檔名及其混亂, 始終沒能弄懂為什麼會這樣? 還請各位同學多多指教!

相關文章