1. webservice是啥
準確的來說,webservice不是一種技術,而是一種規範。是一種跨平臺,跨語言的規範,用於不同平臺,不同語言開發的應用之間的交互。
舉個例子,比如在Windows Server服務器上有個C#.Net開發的應用A,在Linux上有個Java語言開發的應用B,現在B應用要調用A應用,或者是互相調用,用於查看對方的業務數據,就需要webservice的規範。
再舉個例子,天氣預報接口。無數的應用需要獲取天氣預報信息,這些應用可能是各種平臺,各種技術實現,而氣象局的項目,估計也就一兩種,要對外提供天氣預報信息,這個時候,如何解決呢?webservice就是出於以上類似需求而定義出來的規範。
我們一般就是在具體平臺開發webservice接口,以及調用webservice接口,每種開發語言都有自己的webservice實現框架。比如Java 就有 Apache Axis1、Apache Axis2、Codehaus XFire、Apache CXF、Apache Wink、Jboss RESTEasyd等等。其中Apache CXF用的比較多,它也可以和Spring整合。
2. 重溫socket
在分析如何調用webservice前,先來回憶一下傳統的socket是如何通信的,這樣更容易理解ws。
2.1 基於socket創建web服務
爲什麼要使用socket呢?看一下下面的原理圖:
從圖中可以看出,程序A和程序B之間是無法實現直接調用的,那麼現在A需要訪問B的話,A即創建一個socket並制定B機器的端口號,在此之前B已經在本機創建好了socket等待用戶來連接,A和B連接成功後,即可向B發送請求獲取數據了,這很好理解,爲了回憶一下socket的創建和使用,下面先寫一個簡單的socket通信的demo,服務端可以將小寫字母轉大寫。
2.2 經典的socket服務
客戶端:
/**
* @Description Socket客戶端,用來發送給服務端請求
* @author Ni Shengwu
*
*/
public class SocketClient {
public static void main(String[] args) throws Exception {
Scanner input = new Scanner(System.in);
// 1: 創建一個基於TCP協議的socket服務,在建立對象時,要指定連接服務器和端口號
Socket sc = new Socket("127.0.0.1", 9999);
// 2: 通過建立的Socket對象獲取Socket中的輸出流,調用getOutStream方法
OutputStream out = sc.getOutputStream();
System.out.println("請輸入要轉化的字母:");
String initData = input.next();//獲取控制檯的輸入
// 3: 寫入到Socket輸出流中
out.write(initData.getBytes());
System.out.println("等待服務器端返回數據");
// 4: 通過建立的Socket對象獲取Socket中的輸入流,輸入流會接受來自服務器端數據
InputStream in = sc.getInputStream();
byte[] b = new byte[1024];
// 5: 獲取輸入字節流的數據,注意此方法是堵塞的,如果沒有獲取數據會一直等待
int len = in.read(b);
System.out.println("返回的結果爲:" + new String(b, 0, len));
// 關閉Socket
out.close();
in.close();
sc.close();
input.close();
}
}
- 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
服務端:
/**
* @Description Socket服務端,用來接收客戶端請求,實現轉大寫功能
* @author Ni Shengwu
*
*/
public class SocketServer {
public static void main(String[] args) throws Exception {
// 1:建立服務器端的tcp socket服務,必須監聽一個端口
ServerSocket ss = new ServerSocket(9999);
while(true) {
System.out.println("等待客戶端請求……");
// 2: 通過服務器端的socket對象的accept方法獲取連接上的客戶端對象,沒有則堵塞,等待
Socket socket = ss.accept();
System.out.println("握手成功……");
// 3: 通過輸入流獲取數據
InputStream input = socket.getInputStream();
byte[] b = new byte[1024];
int len = input.read(b);
String data = new String(b, 0, len);
System.out.println("客戶端數據爲:" + data);
// 4: 通過服務器端Socket輸出流,寫數據,會傳送到客戶端Socket輸入流中
OutputStream out = socket.getOutputStream();
out.write(data.toUpperCase().getBytes());
// 5: 關閉socket
out.close();
input.close();
socket.close();
}
}
}
- 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
這個demo很簡單,先開啓服務端的程序,在那等待,然後開啓客戶端程序,如果在控制檯輸入hello過去,就會從服務端返回一個HELLO回來,這說明socket通信是成功的。
2.3 web程序訪問socket service
上面經典的demo是在本地寫的兩個java程序,我們現在的很多項目都是web項目,也就是通過瀏覽器來交互的,我們來看下通過瀏覽器的方式如何來訪問socketService服務。服務端還是使用上面的那個java程序,客戶端我改成瀏覽器請求,新寫一個jsp如下:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
</head>
<body>
<form action="http://127.0.0.1:9999" method="post">
<input type="text" name="sname">
<input type="submit" value="提交">
</form>
</body>
</html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
注意看action的請求地址,包括端口要和服務端的一樣,這樣當我們提交的時候就可以訪問上面的服務端程序了。看一下服務端的運行結果:
可以看到,web程序確實和服務端握手成功了,而且數據也是可以傳過去的,sname=hello,包括一些Http信息都可以傳過去,但是我們再來看看服務端返回給瀏覽器的數據是啥:
我用的是chrome瀏覽器,其他瀏覽器可能還沒有數據,這都有可能,但是從數據中來看,它只是單純的把所有信息全部轉成了大寫……而且它也沒有Http的返回格式,也就是說,我所需要的就是個大寫的HELLO即可,所以這是有問題的。
所以可以總結一下:不同的協議其實也是支持Socket通信的。 web程序可以調用socket請求,但是由於協議不同,因此在處理的時候要過濾http的協議格式,返回的時候還需要添加 http返回的格式,否則就會出現問題,可想而知,如果還要處理協議格式,是很麻煩的。
所以到這裏,基本上就理解了爲什麼傳統的socket無法滿足需求了,其實除了上面的弊端外,還有其他的弊端,比如如果參數一多,就不好維護等等,這裏就不多舉例了。
3. 調用已發佈的WebService
關於webservice本身,我就不做過多的描述了,在最上面也有簡單介紹,既然傳統的socket通信無法滿足,那麼下面開始來調用已發佈的ws,真正走進ws的世界。
有一個站點:http://www.webxml.com.cn,是上海的一家公司做的,上面提供了很多ws服務,其中有一個查詢號碼歸屬地的功能,我們用它來做測試。先來在它們的站點中測試一下,然後再在本地寫程序來調用這個ws服務獲取查詢結果。
看一下站點上的查詢:
進入手機號碼歸屬地查詢web服務後,
選擇getMobileCodeInfo,即可進入查詢頁面了,
調用後就會出現<string xmlns="http://WebXml.com.cn/">18312345678:廣東 深圳 廣東移動全球通卡</string>
的結果。這就是調用ws的結果,接下來我們在程序中來調用這個ws。
3.1 get請求方式
在java程序中如果要發送http請求,需要使用HttpClient工具。HttpClient 是 Apache Jakarta Common 下的子項目,可以用來提供高效的、最新的、功能豐富的支持 HTTP 協議的客戶端編程工具包,並且它支持 HTTP 協議最新的版本和建議。
爲什麼要使用HttpClient工具呢?因爲原生態的Socket基於傳輸層,現在我們要訪問的WebService是基於HTTP的屬於應用層,所以我們的Socket通信要藉助HttpClient發HTTP請求,這樣格式才能匹配。
/**
* @Description get方式請求
* @param number
* @throws Exception
*/
public void get(String number) throws Exception {
//HttpClient:在java代碼中模擬Http請求
// 創建瀏覽器對象
HttpClient client = new HttpClient();
// 填寫數據,發送get或者post請求
GetMethod get = new GetMethod("http://ws.webxml.com.cn/WebServices/MobileCodeWS.asmx"
+ "/getMobileCodeInfo?mobileCode=" + number + "&userID=");
// 指定傳輸的格式爲get請求格式
get.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
// 發送請求
int code = client.executeMethod(get);
System.out.println("Http:狀態碼爲:" + code);
String result = get.getResponseBodyAsString();
System.out.println("返回的結果爲:" + result);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
從程序中可以看出,請求的主機是ws.webxml.com.cn,這些url在ws提供方的網站上都有,我們只需要寫對即可請求ws了,在main方法中調用一下該方法即可在控制檯獲取結果。
3.2 post請求方式
請求的過程都一樣,只是url和傳輸格式不同而已,修改一下相應的地方即可,
/**
* @Description post方式請求
* @param number
* @throws Exception
*/
public void post(String number) throws Exception {
//HttpClient:在java代碼中模擬Http請求
// 創建瀏覽器對象
HttpClient client = new HttpClient();
// 填寫數據,發送get或者post請求
PostMethod post = new PostMethod("http://ws.webxml.com.cn/WebServices/MobileCodeWS.asmx/getMobileCodeInfo");
// 指定傳輸的格式爲默認post格式
post.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
// 傳輸參數
post.setParameter("mobileCode", number);
post.setParameter("userID", "");
// 發送請求
int code = client.executeMethod(post);
System.out.println("Http:狀態碼爲:" + code);
String result = post.getResponseBodyAsString();
System.out.println("返回的結果爲:" + result);
}
- 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
3.3 SOAP方式請求
這也是在用的多的方式,它有兩個版本soap1.1和soap1.2,jdk1.7及以上纔可以使用soap1.2。
/**
* @Description soap post方式請求,但是傳輸的數據爲xml格式,有利於數據的維護
* @param number
* @throws Exception
*/
public void soap(String number) throws Exception {
//HttpClient:在java代碼中模擬Http請求
// 創建瀏覽器對象
HttpClient client = new HttpClient();
// 填寫數據,發送get或者post請求
PostMethod post = new PostMethod("http://ws.webxml.com.cn/WebServices/MobileCodeWS.asmx");
// 指定傳輸的格式爲xml格式
post.setRequestHeader("Content-Type", "application/soap+xml;charset=utf-8");
// 傳輸xml,加載soap.txt
post.setRequestBody(new FileInputStream("E:/github/client/src/soap.txt"));
// 發送請求
int code = client.executeMethod(post);
System.out.println("Http:狀態碼爲:" + code);
String result = post.getResponseBodyAsString();
// 如果採用的是soap,則返回的數據也是基於xml的soap格式
System.out.println("返回的結果爲:" + result);
}
- 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
由於soap方式需要向服務端發送xml,所以我們可以實現寫好一個txt文檔,裏面是xml的數據,這個模板ws提供方會提供,我們需要寫好即可:
<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Body>
<getMobileCodeInfo xmlns="http://WebXml.com.cn/">
<mobileCode>18312345678</mobileCode>
<userID></userID>
</getMobileCodeInfo>
</soap12:Body>
</soap12:Envelope>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
上面這些方式推薦使用soap的方式,不過本質上還是http方式調用,只是調用的時候可以傳輸xml數據而已。而且HttpClient是Java的調用http協議的解決方案,但是不能保證其它語言也擁有類似的工具。所以ws推薦的方案是使用wsimport命令。這也是下面分析的重點。
3.4 使用wsimport
每個ws都會有一個WSDL,WSDL即WebService Description Language – Web服務描述語言。它是通過XML形式說明服務在什麼地方-地址。通過XML形式說明服務提供什麼樣的方法 – 如何調用。我們可以通過這個WSDL來獲取和這個ws有關的信息,包括class和java代碼。關於這個WSDL後面我再具體分析,這一節先來看一下如何使用。
wsimport是一個命令,jdk1.6及以上纔可以使用,ws針對不同的語言都會有個wsimport命令,我們可以在自己安裝的jdk的bin目錄下找到這個wsimport.exe,正因爲有了這個,所以我們可以在命令行中使用wsimport命令。怎麼使用呢?
每個ws都會有一個WSDL,就拿上面的歸屬地查詢服務來說,上面第二張圖上面有個服務說明,點開就可以看到WSDL,當然也可以直接訪問瀏覽器上的url來訪問這個WSDL,即xml文檔。如下:
目前只需要複製一下那個url即可,然後打開命令提示符窗口,隨便進入一個目錄下(該目錄要保存等會生成的和ws相關的文件,自己事先建一個即可),運行 wsimport http://ws.webxml.com.cn/WebServices/MobileCodeWS.asmx?WSDL
就會生成相應的javabean,當然了,是.class文件,但是我們不想要class文件,我們想要java文件,所以可以使用如下命令: wsimport -s http://ws.webxml.com.cn/WebServices/MobileCodeWS.asmx?WSDL
這樣不僅生成了class文件,還生成了java文件,如果我們想要在固定的包下生成這些文件,等會方便直接拷貝到項目裏,可以使用下面的命令: wsimport -s . -p ws.client.c http://ws.webxml.com.cn/WebServices/MobileCodeWS.asmx?WSDL
這樣就會在目錄ws/client/c/下生成所需要的class和java代碼,然後我們刪掉class文件,直接拷貝ws目錄到工程中即可,如下(_Main是我自己寫的,用來調用使用的):
這樣就有了號碼歸屬地查詢這個ws服務相關的API了,這是通過官方的WSDL來生成的,然後我們如何在自己的項目中使用呢?我新寫一個_Main.java文件,直接使用這些API即可,如下:
public class _Main {
public static void main(String[] args) {
// 獲取一個ws服務
MobileCodeWS ws = new MobileCodeWS();
// 獲取具體的服務類型:get post soap1.1 soap1.2
MobileCodeWSSoap wsSoap = ws.getMobileCodeWSSoap();
String address = wsSoap.getMobileCodeInfo("18312345678", null);
System.out.println("手機歸屬地信息爲:" + address);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
這樣就很方便了,現在已經完全沒有了上面那種連接啊,設置地址啊等等,直接封裝好了,我直接調用這些API即可調用遠程的webservice。這也是官方推薦的一種方法,當然我們也可以將生成的class文件打包成jar放到工程中。運行一下這個main方法後,也直接返回歸屬地,沒有那些標籤的東西了,這纔是開發中所需要的東西。
到這裏基本已經會調用webservice了,最後再簡單總結一下,ws中這個WSDL很重要,這裏面用xml描述了該ws的信息,所以我們可以通過解析WSDL來獲取該ws相關的API,然後在自己的項目中調用這些API即可調用該ws。