WebService是一種跨編程語言、跨操作系統平台的遠程調用技術,已存在很多年了,很多接口也都是通過WebService方式來發布的,下面這篇文章主要給大家介紹了關於java調用WebService服務的四種方法,需要的朋友可以參考下
一、前言
本來不想寫這個的,因為網上類似的是在是太多了。但是想想自己前面段時間用過,而且以后可能再也沒機會用了。所以還是記錄一下吧。我這兒是以C語言生成的WebService為例。因為通常來說,兩個java端之間的互相通訊沒必要寫成WebService的方式,太麻煩。除非有一方已經固定了是webService的方式(常見於牛逼的甲方)。而且就算寫成了WebService方式兩個java端直接調用也相對比較簡單,因為用java的話很多規范都是自動生成好的,而其他語言就不是這樣了,有時候對方壓根就不是正確的規范,你還不能讓對方改!!!!!我覺得webService這個東西常用於不同語言編寫的服務器之間進行數據交互。因為是基於WSDL的。我知道的主要的方法有以下幾種(我這兒全部以C語言編寫的WebService做優缺點比較):
二、簡介
1、通過axis2將WebService提供的wsdl文件生成對應的java類,這樣就可以相當調用本地類一樣調用webService提供的接口。
優點:調用簡單,無需自己編寫太多的東西。
缺點:大部分情況根據對應的webService生成的服務中地址是固定的,不易更改,而且生成的代碼過於龐大 ,不便於閱讀。同時必須得有webservice對應的的wsdl文件,不太可控。
2、通過RPC方式調用(推薦使用)
優點:自己編寫部分調用代碼,可靈活更換調用的路徑,適合分布式部署的服務器,只需要知道webservice服務的方法名、命名空間、以及對應的參數就好。
缺點:部分特殊情況下可能可以調用成功,但是無法獲取返回值。稍后會進行說明。
3、通過HttpURLConnection進行調用,可用於補充第二種方法的不足之處。
優點:補充RPC方式的不足,代碼編寫量較少。
缺點:(C語言的WebService服務)大部分時候要自己編寫輸入的報文頭,自己解析返回的報文。需要事先抓包查看報文。
4、通過httpclient調用。
和HttpURLConnection原理一樣,只是用不同方法實現。優缺點也差不多。
三、具體解析
第一種方式,首先得下載axis2的jar包,Axis2提供了一個wsdl2java.bat命令可以根據WSDL文件自動產生調用WebService的代碼。
wsdl2java.bat命令可以在<Axis2安裝目錄>/bin目錄中找到。如果你配置了環境變量則可以在控制台中用一下方式
環境變量\bin\wsdl2java,具體如下。
%AXIS2_HOME%\bin\wsdl2java -uri d:demo.wsdl -p client -s -o stub
如果沒有則自己鍵入到對應的位置執行。wsdl2java -uri d:demo.wsdl -p client -s -o stub
其中,-url是對應WebService的wsdl位置,可以是本地的也可以是網絡的。-p是指定生成的類名。具體參數列表如下:
- -o <path> : 指定生成代碼的輸出路徑
- -a : 生成異步模式的代碼
- -s : 生成同步模式的代碼
- -p <pkg> : 指定代碼的package名稱
- -l <languange> : 使用的語言(Java/C) 默認是java
- -t : 為代碼生成測試用例
- -ss : 生成服務端代碼 默認不生成
- -sd : 生成服務描述文件 services.xml,僅與-ss一同使用
- -d <databinding> : 指定databingding,例如,adb,xmlbean,jibx,jaxme and jaxbri
- -g : 生成服務端和客戶端的代碼
- -pn <port_name> : 當WSDL中有多個port時,指定其中一個port
- -sn <serv_name> : 選擇WSDL中的一個service
- -u : 展開data-binding的類
- -r <path> : 為代碼生成指定一個repository
- -ssi : 為服務端實現代碼生成接口類
- -S : 為生成的源碼指定存儲路徑
- -R : 為生成的resources指定存儲路徑
–noBuildXML : 輸出中不生成build.xml文件
–noWSDL : 在resources目錄中不生成WSDL文件
–noMessageReceiver : 不生成MessageReceiver類
生成完后可以在axis2的bin目錄下找到對應的文件。文件和同類.java文件要大很多,並且調用路徑是定死的(標紅部分),改起來麻煩,反正我是不喜歡這種方式。雖然不要自己寫,但是看着這么多行就不爽,太臃腫了。
調用方式如下。(方式應該有多種,沒有去深入研究)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
package
client;
import
javax.xml.namespace.QName;
import
org.apache.axis2.addressing.EndpointReference;
import
org.apache.axis2.client.Options;
import
org.apache.axis2.rpc.client.RPCServiceClient;
public
class
TestAms {
public
static
void
main(String[] args)
throws
Exception
{
AmsStub1 stub=
new
AmsStub1();
AmsStub1.SetAlarmServerCfgMsg setmsg=
new
AmsStub1.SetAlarmServerCfgMsg();
//ServiceStub.SetAlarmServerCfgMsgResponse re=new ServiceStub.SetAlarmServerCfgMsgResponse();
String str=
"{\"name\":\"demo\",\"id\":21,\"code\":\"161021021040288690\"}"
;
//對應的參數
setmsg.setPAlarmCfgMsg(str);
//設置參數
String re=stub.setAlarmServerCfgMsg(setmsg).getResponse();
//調用並獲取返回值
System.out.println(re);
}
}
|
我工作中遇到的全是C語言編寫的WebService,而且每個人編寫的都有些區別,很多人給你的wsdl並不能直接成功生成對應的java類,而且這種方法還有上述的一些缺點,所以我拋棄了這種方法。(上述列的代碼全是以前試驗時用過的,那時候是成功的,寫這個博客的時候我把所有能找到的wsdl全試着生成一遍,結果全部生成失敗。心塞)。
第二種RPC 方式,強烈推薦。
這種方式不多說,直接看代碼就懂了 。這是一個調用webService查詢設備在線數的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
public
String getOnline(String url){
int
errCode=
0
;
JSONObject resultJson=
new
JSONObject();
String result=
""
;
Service service =
new
Service();
Call call;
try
{
call=(Call) service.createCall();
QName opAddEntry =
new
QName(
"urn:demo"
,
"GetOnlineInfo"
);
//設置命名空間和需要調用的方法名
call.setTargetEndpointAddress(url);
//設置請求路徑
call.setOperationName(
"GetNcgOnlineInfo"
);
//調用的方法名
call.setTimeout(Integer.valueOf(
2000
));
//設置請求超時
call.setReturnType(org.apache.axis.encoding.XMLType.XSD_STRING);
//設置返回類型
result= (String) call.invoke(opAddEntry,
new
Object[]{});
}
catch
(ServiceException e) {
// TODO Auto-generated catch block
System.out.println(
"查詢在線狀態1:"
+e.getMessage());
errCode=
1
;
}
catch
(RemoteException e) {
// TODO Auto-generated catch block
System.out.println(
"查詢在線狀態2:"
+e.getMessage());
errCode=
2
;
}
resultJson.put(
"errCode"
, errCode);
resultJson.put(
"data"
, result);
return
resultJson.toString();
}
|
里面注釋比較全。還有些別的設置也比較簡單,自己琢磨就知道了。例如編碼方式、解析時間等。
說說這種方式的問題吧。我在使用的時候遇到的是:和我對接的人編寫了兩個WebService。但是由於這兩個中有許多部分是相同的,他就把這兩個合並了,同時提供了兩個命名空間(具體怎么操作的我也不清楚),那么問題了,這其中有一個命名空間的所有方法我都能成功調用,但是都無法收到返回值。當時我就方了,開始還是好好的,怎么就突然不行了,於是我繼續執行,查看報錯消息,同時抓包查看報文內容。終於給我發現了問題。
下圖是返回結果報的錯,大體意識就是說我設置的命名空間和對方的命名空間不匹配。然后RPC解析就失敗了。
然后我利用Wireshark抓包,得到一下結果。可以看看出,我請求的是命名空間是 ns1="urn:ncg"(其余的都是wsdl默認自帶的)。可是我收到的返回報文就變了。變成了這樣的 xmlns:dag="http://tempuri.org/dag.xsd" xmlns:dag="urn:dag" xmlns:ncg="urn:ncg" 足足有三個啊。RPC按照默認設置的 ns1="urn:ncg" 去解析,那肯定什么都解析不了的。所以只有自己去解析了。這種情況可以利用第三種或者第四種方式進行調用。
第三種:利用HttpURLConnection拼接和解析報文進行調用。
還是上面那個查詢設備的方法。只不過改了下。當然,我這是知道報文后的解決辦法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
public
String ncgConnection(String url,String method){
URL wsUrl;
int
errCode=
0
;
JSONObject resultJson=
new
JSONObject();
String result=
""
;
try
{
wsUrl =
new
URL(url+
"/"
+method);
HttpURLConnection conn = (HttpURLConnection) wsUrl.openConnection();
conn.setDoInput(
true
);
conn.setDoOutput(
true
);
conn.setRequestMethod(
"POST"
);
conn.setRequestProperty(
"Content-Type"
,
"text/xml;charset=UTF-8"
);
conn.setConnectTimeout(
2000
);
conn.setReadTimeout(
2000
);
OutputStream os = conn.getOutputStream();
//請求體
//<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><soapenv:Body><ns1:DeleteCascadeFromCms soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="urn:ncg"><ncg-code-list xsi:type="xsd:string">["11241525"]</ncg-code-list></ns1:DeleteCascadeFromCms></soapenv:Body></soapenv:Envelope>
String soap =
"<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" "
+
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"><soapenv:Body><ns1:"
+method+
" soapenv:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:ns1=\"urn:ncg\"/></soapenv:Body></soapenv:Envelope>"
;
os.write(soap.getBytes());
InputStream is = conn.getInputStream();
byte
[] b =
new
byte
[
1024
];
int
len =
0
;
String s =
""
;
while
((len = is.read(b)) != -
1
){
String ss =
new
String(b,
0
,len,
"UTF-8"
);
s += ss;
}
result=s.split(
"<response xsi:type=\"xsd:string\">"
)[
1
].split(
"</response>"
)[
0
];
is.close();
os.close();
conn.disconnect();
}
catch
(MalformedURLException e) {
// TODO Auto-generated catch block
System.out.println(
"通訊模塊1:"
+e.getMessage());
errCode=
1
;
}
catch
(IOException e) {
// TODO Auto-generated catch block
System.out.println(
"通訊模塊2:"
+e.getMessage());
errCode=
2
;
}
resultJson.put(
"errCode"
, errCode);
resultJson.put(
"data"
, result);
return
resultJson.toString();
}
|
正常來說,利用HttpURLConnection實現很多的調用不需要自己拼接請求頭和解析返回結果的(例如java端提供的一些action或者controller),可是在這兒調用WebService,確確實實的需要自己手寫。對比上面那個Wireshark抓包的結果可以發現,在請求體部分按照對方提供的wsdl進行拼接,結果部分也進行相同的解析。可以正確獲得結果。
第四種,利用httpclient
簡單來說,httpClient可以算是加強版的HttpURLConnection,httpClient的API比較多,也比較穩定,不容易擴展。HttpURLConnection比較輕量級,容易根據自己的需求進行擴展。但是穩定性不如httpClient。
這種方法具體實現思路和HttpURLConnection一樣。只是有點小區別。代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
public
void
demo(String url){
HttpClient httpClient=
new
HttpClient();
PostMethod postMethod=
new
PostMethod();
postMethod.setPath(url+
"/ncg.wsdl"
);
//路徑和wsdl名
String soap =
"<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" "
+
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"><soapenv:Body><ns1:GetNcgOnlineInfo soapenv:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:ns1=\"urn:ncg\"/></soapenv:Body></soapenv:Envelope>"
;
try
{
byte
[] b=soap.getBytes(
"utf-8"
);
InputStream is =
new
ByteArrayInputStream(b,
0
, b.length);
RequestEntity re =
new
InputStreamRequestEntity(is, b.length,
"application/soap+xml; charset=utf-8"
);
postMethod.setRequestEntity(re);
int
statusCode = httpClient.executeMethod(postMethod);
String soapResponseData = postMethod.getResponseBodyAsString();
postMethod.releaseConnection();
//解析
System.out.println(soapResponseData.split(
"<response xsi:type=\"xsd:string\">"
)[
1
].split(
"</response>"
)[
0
]);
}
catch
(UnsupportedEncodingException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
catch
(HttpException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch
(IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
|
結果:我這兒沒有做更多的判斷,直接輸出,這種方式我以前其實並沒有用到。如果有需要可以更具返回的狀態判斷是否成功。如果你去抓包的話,你會發現這個會和上面HttpURLConnection抓的一樣。
總結:調用webService很多程度上需要依賴對方編寫WebService是否嚴謹,如果足夠嚴謹,推薦使用RPC方式編寫,其余的更具實際情況進行選擇