Axis2是一套嶄新的WebService引擎,該版本是對Axis1.x重新設計的産物。Axis2不僅支援SOAP1.1和SOAP1.2,還內建了非常流行的REST WebService,同時還支援Spring、JSON等技術。這些都将在後面的系列教程中講解。在本文中主要介紹了如何使用Axis2開發一個不需要任何配置檔案的WebService,并在用戶端使用Java和C#調用這個WebService。
一、Axis2的下載下傳和安裝
讀者可以從如下的網址下載下傳Axis2的最新版本:
http://ws.apache.org/axis2/
在本文使用了目前Axis2的最新版本1.4.1。讀者可以下載下傳如下兩個zip包:
axis2-1.4.1-bin.zip
axis2-1.4.1-war.zip
其中axis2-1.4.1-bin.zip檔案中包含了Axis2中所有的jar檔案, axis2-1.4.1-war.zip檔案用于将WebService釋出到Web容器中。
将axis2-1.4.1-war.zip檔案解壓到相應的目錄,将目錄中的axis2.war檔案放到<Tomcat安裝目錄>\webapps目錄中(本文使用的Tomcat的版本是6.x),并啟動Tomcat。
在浏覽器位址欄中輸入如下的URL:
http://localhost:8080/axis2/
如果在浏覽器中顯示出如圖1所示的頁面,則表示Axis2安裝成功。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicGcq5SMw8CXx8lMzlGeh9CX5V3Zhl2av52LcRXZu9VY2Fman9Gbi9CXzV2Zh1WavwFdl5mLhZXYqd2bsJmL3d3dvw1LcpDc0RHaiojIsJye.jpg)
圖1
二、編寫和釋出WebService
對于用Java實作的服務程式給人的印象就是需要進行大量的配置,不過這一點在Axis2中将被終結。在Axis2中不需要進行任何的配置,就可以直接将一個簡單的POJO釋出成WebService。其中POJO中所有的public方法将被釋出成WebService方法。
下面我們來實作一個簡單的POJO,代碼如下:
public class SimpleService
{
public String getGreeting(String name)
{
return "你好 " + name;
}
public int getPrice()
{
return new java.util.Random().nextInt(1000);
}
}
在SimpleService類中有兩個方法,由于這兩個方法都是public方法,是以,它們都将作為WebService方法被釋出。
編譯SimpleService類後,将SimpleService.class檔案放到<Tomcat安裝目錄>\webapps\axis2\WEB-INF\pojo目錄中(如果沒有pojo目錄,則建立該目錄)。現在我們已經成功将SimpleService類釋出成了WebService。在浏覽器位址欄中輸入如下的URL:
http://localhost:8080/axis2/services/listServices
這時目前頁面将顯示所有在Axis2中釋出的WebService,如圖2所示。
圖2
在浏覽器位址欄中輸入如下的兩個URL來分别測試getGreeting和getPrice方法:
http://localhost:8080/axis2/services/SimpleService/getGreeting?name=bill
http://localhost:8080/axis2/services/SimpleService/getPrice
圖3和圖4分别顯示了getGreeting和getPrice方法的測試結果。
圖3 getGreeting方法的測試結果
圖4 getPrice方法的測試結果
在編寫、釋出和測試0配置的WebService時應注意如下幾點:
1. POJO類不能使用package關鍵字聲明包。
2. Axis2在預設情況下可以熱釋出WebService,也就是說,将WebService的.class檔案複制到pojo目錄中時,Tomcat不需要重新啟動就可以自動釋出WebService。如果想取消Axis2的熱釋出功能,可以打開<Tomcat安裝目錄>\webapps\axis2\WEB-INF\conf\axis2.xml,找到如下的配置代碼:
< parameter name ="hotdeployment" >true </ parameter >
将true改為false即可。要注意的是,Axis2在預設情況下雖然是熱釋出,但并不是熱更新,也就是說,一旦成功釋出了WebService,再想更新該WebService,就必須重新開機Tomcat。這對于開發人員調試WebService非常不友善,是以,在開發WebService時,可以将Axis2設為熱更新。在axis2.xml檔案中找到<parameter name="hotupdate">false</parameter>,将false改為true即可。
3. 在浏覽器中測試WebService時,如果WebService方法有參數,需要使用URL的請求參數來指定該WebService方法參數的值,請求參數名與方法參數名要一緻,例如,要測試getGreeting方法,請求參數名應為name,如上面的URL所示。
4. 釋出WebService的pojo目錄隻是預設的,如果讀者想在其他的目錄釋出WebService,可以打開axis2.xml檔案,并在<axisconfig>元素中添加如下的子元素:
< deployer extension =".class" directory ="my" class ="org.apache.axis2.deployment.POJODeployer" />
上面的配置允許在<Tomcat安裝目錄>\webapps\axis2\WEB-INF\my目錄中釋出WebService。例如,将本例中的SimpleService.class複制到my目錄中也可以成功釋出(但要删除pojo目錄中的SimpleService.class,否則WebService會重名)。
三、 用Java實作調用WebService的用戶端程式
WebService是為程式服務的,隻在浏覽器中通路WebService是沒有意義的。是以,在本節使用Java實作了一個控制台程式來調用上一節釋出的WebService。調用WebService的用戶端代碼如下:
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 RPCClient
{
public static void main(String[] args) throws Exception
{
// 使用RPC方式調用WebService
RPCServiceClient serviceClient = new RPCServiceClient();
Options options = serviceClient.getOptions();
// 指定調用WebService的URL
EndpointReference targetEPR = new EndpointReference(
"http://localhost:8080/axis2/services/SimpleService");
options.setTo(targetEPR);
// 指定getGreeting方法的參數值
Object[] opAddEntryArgs = new Object[] {"超人"};
// 指定getGreeting方法傳回值的資料類型的Class對象
Class[] classes = new Class[] {String. class};
// 指定要調用的getGreeting方法及WSDL檔案的命名空間
QName opAddEntry = new QName("http://ws.apache.org/axis2", "getGreeting");
// 調用getGreeting方法并輸出該方法的傳回值
System.out.println(serviceClient.invokeBlocking(opAddEntry, opAddEntryArgs, classes)[0]);
// 下面是調用getPrice方法的代碼,這些代碼與調用getGreeting方法的代碼類似
classes = new Class[] { int. class};
opAddEntry = new QName("http://ws.apache.org/axis2", "getPrice");
System.out.println(serviceClient.invokeBlocking(opAddEntry, new Object[]{}, classes)[0]);
}
}
運作上面的程式後,将在控制台輸出如下的資訊:
你好 超人
443
在編寫用戶端代碼時應注意如下幾點:
1. 用戶端代碼需要引用很多Axis2的jar包,如果讀者不太清楚要引用哪個jar包,可以在Eclipse的工程中引用Axis2發行包的lib目錄中的所有jar包。
2. 在本例中使用了RPCServiceClient類的invokeBlocking方法調用了WebService中的方法。invokeBlocking方法有三個參數,其中第一個參數的類型是QName對象,表示要調用的方法名;第二個參數表示要調用的WebService方法的參數值,參數類型為Object[];第三個參數表示WebService方法的傳回值類型的Class對象,參數類型為Class[]。當方法沒有參數時,invokeBlocking方法的第二個參數值不能是null,而要使用new Object[]{}。
3. 如果被調用的WebService方法沒有傳回值,應使用RPCServiceClient類的invokeRobust方法,該方法隻有兩個參數,它們的含義與invokeBlocking方法的前兩個參數的含義相同。
4. 在建立QName對象時,QName類的構造方法的第一個參數表示WSDL檔案的命名空間名,也就是<wsdl:definitions>元素的targetNamespace屬性值,下面是SimpleService類生成的WSDL檔案的代碼片段:
<? xml version="1.0" encoding="UTF-8" ?>
< wsdl:definitions xmlns:wsdl ="http://schemas.xmlsoap.org/wsdl/" xmlns:ns1 ="http://org.apache.axis2/xsd"
xmlns:ns ="http://ws.apache.org/axis2" xmlns:wsaw ="http://www.w3.org/2006/05/addressing/wsdl"
xmlns:http ="http://schemas.xmlsoap.org/wsdl/http/" xmlns:xs ="http://www.w3.org/2001/XMLSchema"
xmlns:mime ="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:soap ="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:soap12 ="http://schemas.xmlsoap.org/wsdl/soap12/"
targetNamespace="http://ws.apache.org/axis2" >
< wsdl:types >
</ wsdl:types >
</ wsdl:definitions >
四、用wsdl2java簡化用戶端的編寫
也許有很多讀者會說“有沒有搞錯啊,隻調用兩個WebService方法用要寫這麼多代碼,太麻煩了”。
不過幸好Axis2提供了一個wsdl2java.bat指令可以根據WSDL檔案自動産生調用WebService的代碼。wsdl2java.bat指令可以在<Axis2安裝目錄>"bin目錄中找到。在使用wsdl2java.bat指令之前需要設定AXIS2_HOME環境變量,該變量值是<Axis2安裝目錄>。
在Windows控制台輸出如下的指令行來生成調用WebService的代碼:
%AXIS2_HOME%\bin\wsdl2java -uri http://localhost:8080/axis2/services/SimpleService?wsdl -p client -s -o stub
其中-url參數指定了wsdl檔案的路徑,可以是本地路徑,也可以是網絡路徑。-p參數指定了生成的Java類的包名,-o參數指定了生成的一系列檔案儲存的根目錄。在執行完上面的指令後,讀者就會發現在目前目錄下多了個stub目錄,在."stub"src"client目錄可以找到一個SimpleServiceStub.java檔案,該檔案複雜調用WebService,讀者可以在程式中直接使用這個類,代碼如下:
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 StubClient
{
public static void main(String[] args) throws Exception
{
SimpleServiceStub stub = new SimpleServiceStub();
SimpleServiceStub.GetGreeting gg = new SimpleServiceStub.GetGreeting();
gg.setName("比爾");
System.out.println( stub.getGreeting(gg).get_return());
System.out.println(stub.getPrice().get_return());
}
}
上面的代碼大大簡化了調用WebService的步驟,并使代碼更加簡潔。但要注意的是,wsdl2java.bat指令生成的Stub類将WebService方法的參數都封裝在了相應的類中,類名為方法名,例如,getGreeting方法的參數都封裝在了GetGreeting類中,要想調用getGreeting方法,必須先建立GetGreeting類的對象執行個體。
五、使用C#調用WebService
從理論上說,WebService可以被任何支援SOAP協定的語言調用。在Visual Studio中使用C#調用WebService是在所有語言中最容易實作的(VB.net的調用方法類似,也同樣很簡單)。
建立一個Visual Studio工程,并在引用Web服務的對話框中輸入如下的URL,并輸入Web引用名為“WebService”:
http://localhost:8080/axis2/services/SimpleService?wsdl
然後引用Web服務的對話框就會顯示該WebService中的所有的方法,如圖5所示。
圖5
在完成上面的工作後,隻需要如下三行C#代碼就可以調用getGreeting和getPrice方法,并顯示這兩個方法的傳回值:
WebService.SimpleService simpleService = new WSC.WebService.SimpleService();
MessageBox.Show( simpleService.getGreeting("比爾"));
MessageBox.Show(simpleService.getPrice()[email protected]());
在.net解析WSDL檔案時直接将getGreeting方法的參數映射為String類型,是以,可以直接進行傳值。
從上面的調用過程可以看出,添加Web引用的過程就相當于在Java中調用wsdl2java.bat自動生成stub類的過程。隻是在調用stub類時與C#有一定的差別,但從總體上來說,都大大簡化了調用WebService的過程。
在實際的應用中,不僅需要使用WebService來傳遞簡單類型的資料,有時也需要傳遞更複雜的資料,這些資料可以被稱為複合類型的資料。數組與類(接口)是比較常用的複合類型。在Axis2中可以直接使用将WebService方法的參數或傳回值類型聲明成數組或類(接口)。但要注意,在定義數組類型時隻能使用一維數組,如果想傳遞多元數組,可以使用分隔符進行分隔,如下面的代碼所示:
String[] strArray = new String[]{ "自行車,飛機,火箭","中國,美國,德國", "超人,蜘蛛俠,鋼鐵俠" } ;
上面的代碼可以看作是一個3*3的二維數組。
在傳遞類的對象執行個體時,除了直接将數組類型聲明成相應的類或接口,也可以将對象執行個體進行序列化,也就是說,将一個對象執行個體轉換成位元組數組進行傳遞,然後接收方再進行反序列化,還原這個對象執行個體。
下面的示例代碼示範了如何傳遞數組與類(接口)類型的資料,并示範如何使用位元組數組上傳圖像。本示例的用戶端代碼使用Java和C#編寫。要完成這個例子需要如下幾步:
一、實作服務端代碼
ComplexTypeService是一個WebService類,該類的代碼如下:
import java.io.FileOutputStream;
import data.DataForm;
public class ComplexTypeService
{
// 上傳圖像,imageByte參數表示上傳圖像檔案的位元組,
// length參數表示圖像檔案的位元組長度(該參數值可能小于imageByte的數組長度)
public boolean uploadImageWithByte( byte[] imageByte, int length)
{
FileOutputStream fos = null;
try
{
// 将上傳的圖像儲存在D盤的test1.jpg檔案中
fos = new FileOutputStream("d:\\test1.jpg");
// 開始寫入圖像檔案的位元組
fos.write(imageByte, 0, length);
fos.close();
}
catch (Exception e)
{
return false;
}
finally
{
if (fos != null)
{
try
{
fos.close();
}
catch (Exception e)
{
}
}
}
return true;
}
// 傳回一維字元串數組
public String[] getArray()
{
String[] strArray = new String[]{ "自行車", "飛機", "火箭" };
return strArray;
}
// 傳回二維字元串數組
public String[] getMDArray()
{
String[] strArray = new String[]{ "自行車,飛機,火箭","中國,美國,德國", "超人,蜘蛛俠,鋼鐵俠" } ;
return strArray;
}
// 傳回DataForm類的對象執行個體
public DataForm getDataForm()
{
return new DataForm();
}
// 将DataForm類的對象執行個體序列化,并傳回序列化後的位元組數組
public byte[] getDataFormBytes() throws Exception
{
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
java.io.ObjectOutputStream oos = new java.io.ObjectOutputStream(baos);
oos.writeObject( new DataForm());
return baos.toByteArray();
}
}
二、實作DataForm類
DataForm是要傳回的對象執行個體所對應的類,該類的實作代碼如下:
package data;
public class DataForm implements java.io.Serializable
{
private String name = "bill";
private int age = 20;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public int getAge()
{
return age;
}
public void setAge( int age)
{
this.age = age;
}
}
三、釋出WebService
由于本示例的WebService類使用了一個Java類(DataForm類),是以,在釋出WebService之前,需要先将DataForm.class檔案複制到<Tomcat安裝目錄>\webapps\axis2\WEB-INF\classes\data目錄中,然後将ComplexTypeService.class檔案複制到<Tomcat安裝目錄>\webapps\axis2\WEB-INF\pojo目錄中,最後啟動Tomcat(如果Tomcat已經啟動,由于增加了一個DataForm類,是以,需要重新啟動Tomcat)。
四、使用Java編寫調用WebService的用戶端代碼
在用戶端仍然使用了RPC的調用方式,代碼如下:
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 ComplexTypeRPCClient
{
public static void main(String[] args) throws Exception
{
RPCServiceClient serviceClient = new RPCServiceClient();
Options options = serviceClient.getOptions();
EndpointReference targetEPR = new EndpointReference(
"http://localhost:8080/axis2/services/ComplexTypeService");
options.setTo(targetEPR);
// 下面的代碼調用uploadImageWithByte方法上傳圖像檔案
/
// 打開圖像檔案,确定圖像檔案的大小
java.io.File file = new java.io.File("f:\\images.jpg");
java.io.FileInputStream fis = new java.io.FileInputStream("f:\\images.jpg");
// 建立儲存要上傳的圖像檔案内容的位元組數組
byte[] buffer = new byte[( int) file.length()];
// 将圖像檔案的内容讀取buffer數組中
int n = fis.read(buffer);
System.out.println("檔案長度:" + file.length());
Object[] opAddEntryArgs = new Object[]{ buffer, n };
Class[] classes = new Class[]{ Boolean. class };
QName opAddEntry = new QName("http://ws.apache.org/axis2","uploadImageWithByte");
fis.close();
// 開始上傳圖像檔案,并輸出uploadImageWithByte方法的傳回傳
System.out.println(serviceClient.invokeBlocking(opAddEntry,opAddEntryArgs, classes)[0]);
/
// 下面的代碼調用了getArray方法,并傳回一維String數組
/
opAddEntry = new QName("http://ws.apache.org/axis2", "getArray");
String[] strArray = (String[]) serviceClient.invokeBlocking(opAddEntry,
new Object[]{}, new Class[]{String[]. class })[0];
for (String s : strArray)
System.out.print(s + " ");
System.out.println();
/
// 下面的代碼調用了getMDArray方法,并傳回一維String數組
/
opAddEntry = new QName("http://ws.apache.org/axis2", "getMDArray");
strArray = (String[]) serviceClient.invokeBlocking(opAddEntry, new Object[]{},
new Class[]{String[]. class})[0];
for (String s : strArray)
{
String[] array = s.split(",");
for(String ss: array)
System.out.print("<" + ss + "> ");
System.out.println();
}
System.out.println();
/
// 下面的代碼調用了getDataForm方法,并傳回DataForm對象執行個體
/
opAddEntry = new QName("http://ws.apache.org/axis2", "getDataForm");
data.DataForm df = (data.DataForm) serviceClient.invokeBlocking(opAddEntry, new Object[]{},
new Class[]{data.DataForm. class})[0];
System.out.println(df.getAge());
/
// 下面的代碼調用了getDataFormBytes方法,并傳回位元組數組,最後将傳回的位元組數組反序列化後,轉換成DataForm對象執行個體
/
opAddEntry = new QName("http://ws.apache.org/axis2", "getDataFormBytes");
buffer = ( byte[]) serviceClient.invokeBlocking(opAddEntry, new Object[]{}, new Class[]{ byte[]. class})[0];
java.io.ObjectInputStream ois = new java.io.ObjectInputStream(
new java.io.ByteArrayInputStream(buffer));
df = (data.DataForm) ois.readObject();
System.out.println(df.getName());
//
}
}
運作上面的程式,将輸出如下的内容:
檔案長度:3617
true
自行車 飛機 火箭
<自行車> <飛機> <火箭>
<中國> <美國> <德國>
<超人> <蜘蛛俠> <鋼鐵俠>
20
bill
五、使用C#編寫調用WebService的用戶端代碼
在Visual Studio中使用WebService就簡單得多。假設引用WebService時的引用名為complexType,則下面的代碼調用了uploadImageWithByte方法來上傳圖像檔案。在Visual Studio引用WebService時,uploadImageWithByte方法多了兩個out參數,在使用時要注意。
complexType.ComplexTypeService cts = new WSC.complexType.ComplexTypeService();
System.IO.FileStream fs = new System.IO.FileStream(@"f:\images.jpg", System.IO.FileMode.Open);
byte[] buffer = new byte[fs.Length];
fs.Read(buffer, 0, ( int)fs.Length);
bool r;
bool rs;
cts.uploadImageWithByte( buffer, ( int)fs.Length, true, out r, out rs);
在獲得二維數組時,可以将資料加載到DataGridView或其他類似的控件中,代碼如下:
String[] strArray = cts.getMDArray();
for ( int i = 0; i < strArray.Length; i++)
{
// 用正規表達式将帶分隔符的字元串轉換成String數組
String[] columns = strArray[i].Split(',');
// 如果DataGridView的表頭不存在,向DataGridView控件添加三個帶表頭的列
if (dataGridView1.Columns.Count == 0)
for ( int j = 0; j < columns.Length; j++)
dataGridView1.Columns.Add("column" + (j + 1).ToString(), "列" + (j + 1).ToString());
// 添加行
dataGridView1.Rows.Add(1);
for( int j = 0; j < columns.Length; j++)
{
dataGridView1.Rows[i].Cells[j].Value = columns[j];
}
}
向DataGridView控件添加資料後的效果如圖1所示。
圖1
對于其他的WebService方法的調用都非常簡單,讀者可以自己做這個實驗。
要注意的是,由于.net和java序列化和反序列化的差異,通過序列化的方式傳遞對象執行個體隻使用于用戶端與服務端為同一種語言或技術的情況,如用戶端和服務端都使用Java來編寫。
如果讀者要上傳大檔案,應盡量使用FTP的方式來傳遞,而隻通過WebService方法來傳遞檔案名等資訊。這樣有助于提高傳輸效率。
用Axis2實作Web Service,雖然可以将POJO類放在axis2\WEB-INF\pojo目錄中直接釋出成Web Service,這樣做不需要進行任何配置,但這些POJO類不能在任何包中。這似乎有些不友善,為此,Axis2也允許将帶包的POJO類釋出成Web Service。
先實作一個POJO類,代碼如下:
package service;
public class MyService
{
public String getGreeting(String name)
{
return "您好 " + name;
}
public void update(String data)
{
System.out.println("<" + data + ">已經更新");
}
}
這個類有兩個方法,這兩個方法都需要釋出成Web Service方法。這種方式和直接放在pojo目錄中的POJO類不同。要想将MyService類釋出成Web Service,需要一個services.xml檔案,這個檔案需要放在META-INF目錄中,該檔案的内容如下:
< service name ="myService" >
< description >
Web Service例子
</ description >
< parameter name ="ServiceClass" >
service.MyService
</ parameter >
< messageReceivers >
< messageReceiver mep ="http://www.w3.org/2004/08/wsdl/in-out"
class ="org.apache.axis2.rpc.receivers.RPCMessageReceiver" />
< messageReceiver mep ="http://www.w3.org/2004/08/wsdl/in-only"
class ="org.apache.axis2.rpc.receivers.RPCInOnlyMessageReceiver" />
</ messageReceivers >
</ service >
其中<service>元素用于釋出Web Service,一個<service>元素隻能釋出一個WebService類,name屬性表示WebService名,如下面的URL可以獲得這個WebService的WSDL内容:
http://localhost:8080/axis2/services/myService?wsdl
其中name屬性名就是上面URL中"?"和"/"之間的部分。
<description>元素表示目前Web Service的描述,<parameter>元素用于設定WebService的參數,在這裡用于設定WebService對應的類名。在這裡最值得注意的是<messageReceivers>元素,該元素用于設定處理WebService方法的處理器。例如,getGreeting方法有一個傳回值,是以,需要使用可處理輸入輸出的RPCMessageReceiver類,而update方法沒有傳回值,是以,需要使用隻能處理輸入的RPCInOnlyMessageReceiver類。
使用這種方式釋出WebService,必須打包成.aar檔案,..aar檔案實際上就是改變了擴充名的.jar檔案。在現在建立了兩個檔案:MyService.java和services.xml。将MyService.java編譯,生成MyService.class。services.xml和MyService.class檔案的位置如下:
D:\ws\service\MyService.class
D:\ws\META-INF\services.xml
在windows控制台中進入ws目錄,并輸入如下的指令生成.aar檔案(實際上,.jar檔案也可以釋出webservice,但axis2官方文檔中建議使用.aar檔案釋出webservice):
jar cvf ws.aar .
最後将ws.aar檔案複制到<Tomcat安裝目錄>\webapps\axis2\WEB-INF\services目錄中,啟動Tomcat後,就可以調用這個WebService了。調用的方法和《WebService大講堂之Axis2(1):用POJO實作0配置的WebService》所講的方法類似。
另外services.xml檔案中也可以直接指定WebService類的方法,如可以用下面的配置代碼來釋出WebService:
< service name ="myService" >
< description >
Web Service例子
</ description >
< parameter name ="ServiceClass" >
service.MyService
</ parameter >
< operation name ="getGreeting" >
< messageReceiver class ="org.apache.axis2.rpc.receivers.RPCMessageReceiver" />
</ operation >
< operation name ="update" >
< messageReceiver
class ="org.apache.axis2.rpc.receivers.RPCInOnlyMessageReceiver" />
</ operation >
</ service >
上面的配置代碼前面的部分和以前的services.xml檔案的内容相同,但後面使用了<operation>元素來指定每一個WebService方法,并單獨指定了處理每一個方法的處理器。對于用戶端來說,調用使用這兩個services.xml檔案釋出的WebService并沒有太大我差別,隻是使用第二個services.xml檔案釋出WebServices後,在使用wsdl2java指令或使用C#、delphi等生成用戶端的stub時,update方法的String類型被封裝在了update類中,在傳遞update方法的參數時需要建立update類的對象執行個體。而使用第一個services.xml檔案釋出的WebService在生成stub時直接可以為update方法傳遞String類型的參數。從這一點可以看出,這兩種方法生成的WSDL有一定的差別。但實際上,如果用戶端程式使用第一個services.xml檔案釋出的WebService生成stub類時(這時update方法的參數是String),在服務端又改為第二個services.xml檔案來釋出WebService,這時用戶端并不需要再重新生成stub類,而可以直接調用update方法。也就是說,服務端使用什麼樣的方式釋出WebService,對用戶端并沒有影響。
如果想釋出多個WebService,可以使用<serviceGroup>元素,如再建立一個MyService1類,代碼如下:
package service
public class MyService1
{
public String getName()
{
return "bill";
}
}
在services.xml檔案中可以使用如下的配置代碼來配置MyService和MyService1類:
< serviceGroup >
< service name ="myService" >
< description >
Web Service例子
</ description >
< parameter name ="ServiceClass" >
service.MyService
</ parameter >
< messageReceivers >
< messageReceiver mep ="http://www.w3.org/2004/08/wsdl/in-out"
class ="org.apache.axis2.rpc.receivers.RPCMessageReceiver" />
< messageReceiver mep ="http://www.w3.org/2004/08/wsdl/in-only"
class ="org.apache.axis2.rpc.receivers.RPCInOnlyMessageReceiver" />
</ messageReceivers >
</ service >
< service name ="myService1" >
< description >
Web Service例子
</ description >
< parameter name ="ServiceClass" >
service.MyService1
</ parameter >
< messageReceivers >
< messageReceiver mep ="http://www.w3.org/2004/08/wsdl/in-out"
class ="org.apache.axis2.rpc.receivers.RPCMessageReceiver" />
< messageReceiver mep ="http://www.w3.org/2004/08/wsdl/in-only"
class ="org.apache.axis2.rpc.receivers.RPCInOnlyMessageReceiver" />
</ messageReceivers >
</ service >
</ serviceGroup >
在《WebService大講堂之Axis2(2):複合類型資料的傳遞》中講過,如果要傳遞二進制檔案(如圖像、音頻檔案等),可以使用byte[]作為資料類型進行傳遞,然後用戶端使用RPC方式進行調用。這樣做隻是其中的一種方法,除此之外,在用戶端還可以使用wsdl2java指令生成相應的stub類來調用WebService,wsdl2java指令的用法詳見《WebService大講堂之Axis2(1):用POJO實作0配置的WebService》。
WebService類中包含byte[]類型參數的方法在wsdl2java生成的stub類中對應的資料類型不再是byte[]類型,而是javax.activation.DataHandler。DataHandler類是專門用來映射WebService二進制類型的。
在WebService類中除了可以使用byte[]作為傳輸二進制的資料類型外,也可以使用javax.activation.DataHandler作為資料類型。不管是使用byte[],還是使用javax.activation.DataHandler作為WebService方法的資料類型,使用wsdl2java指令生成的stub類中相應方法的類型都是javax.activation.DataHandler。而象使用.net、delphi生成的stub類的相應方法類型都是byte[]。這是由于javax.activation.DataHandler類是Java特有的,對于其他語言和技術來說,并不認識javax.activation.DataHandler類,是以,也隻有使用最原始的byte[]了。
下面是一個上傳二進制檔案的例子,WebService類的代碼如下:
package service;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileOutputStream;
import javax.activation.DataHandler;
public class FileService
{
// 使用byte[]類型參數上傳二進制檔案
public boolean uploadWithByte( byte[] file, String filename)
{
FileOutputStream fos = null;
try
{
fos = new FileOutputStream(filename);
fos.write(file);
fos.close();
}
catch (Exception e)
{
return false;
}
finally
{
if (fos != null)
{
try
{
fos.close();
}
catch (Exception e)
{
}
}
}
return true;
}
private void writeInputStreamToFile(InputStream is, OutputStream os) throws Exception
{
int n = 0;
byte[] buffer = new byte[8192];
while((n = is.read(buffer)) > 0)
{
os.write(buffer, 0, n);
}
}
// 使用DataHandler類型參數上傳檔案
public boolean uploadWithDataHandler(DataHandler file, String filename)
{
FileOutputStream fos = null;
try
{
fos = new FileOutputStream(filename);
// 可通過DataHandler類的getInputStream方法讀取上傳資料
writeInputStreamToFile(file.getInputStream(), fos);
fos.close();
}
catch (Exception e)
{
return false;
}
finally
{
if (fos != null)
{
try
{
fos.close();
}
catch (Exception e)
{
}
}
}
return true;
}
}
上面代碼在services.xml檔案的配置代碼如下:
< service name ="fileService" >
< description >
檔案服務
</ description >
< parameter name ="ServiceClass" >
service.FileService
</ parameter >
< messageReceivers >
< messageReceiver mep ="http://www.w3.org/2004/08/wsdl/in-out"
class ="org.apache.axis2.rpc.receivers.RPCMessageReceiver" />
</ messageReceivers >
</ service >
如果使用wsdl2java指令生成調用Java用戶端代碼,則需要建立DataHandler類的對象執行個體,代碼如下:
DataHandler dh = new DataHandler( new FileDataSource(imagePath));
wsdl2java指令會為每一個方法生成一個封裝方法參數的類,類名為方法名(第一個字元大寫),如uploadWithByte方法生成的類名為UploadWithByte。如果要設定file參數的值,可以使用UploadWithByte類的setFile方法,代碼如下:
UploadWithByte uwb = new UPloadWithByte();
uwb.setFile(dh);
最後是調用uploadWithByte方法,代碼如下(FileServiceStub為wsdl2java生成的stub類名):
FileServiceStub fss = new FileServiceStub();
fss.uploadWithByte(uwb);
如果使用C#調用FileService,則file參數類型均為byte[],代碼如下:
MemoryStream ms = new MemoryStream();
Bitmap bitmap = new Bitmap(picUpdateImage.Image);
bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
service.fileService fs = new WSC.service.fileService();
fs.uploadWithDataHandler(ms.ToArray());
fs.uploadWithByte(ms.ToArray());
其中picUpdateImage為c#中加載圖像檔案的picturebox控件。
WebService給人最直覺的感覺就是由一個個方法組成,并在用戶端通過SOAP協定調用這些方法。這些方法可能有傳回值,也可能沒有傳回值。雖然這樣可以完成一些工具,但這些被調用的方法是孤立的,當一個方法被調用後,在其他的方法中無法獲得這個方法調用後的狀态,也就是說無法保留狀态。
讀者可以想象,這對于一個完整的應用程式,無法保留狀态,就意味着隻依靠WebService很難完成全部的工作。例如,一個完整的應用系統都需要進行登入,這在Web應用中使用Session來儲存使用者登入狀态,而如果用WebService的方法來進行登入處理,無法儲存登入狀态是非常令人尴尬的。當然,這也可以通過其他的方法來解決,如在服務端使用static變量來儲存使用者狀态,并發送一個id到用戶端,通過在服務端和用戶端傳遞這個id來取得相應的使用者狀态。這非常類似于Web應用中通過Session和Cookie來管理使用者狀态。但這就需要由開發人員做很多工作,不過幸好Axis2為我們提供了WebService狀态管理的功能。
使用Axis2來管理WebService的狀态基本上對于開發人員是透明的。在WebService類需要使用org.apache.axis2.context.MessageContext和org.apache.axis2.context.ServiceContext類來儲存與獲得儲存在服務端的狀态資訊,這有些象使用HttpSession接口的getAttribute和setAttribute方法獲得與設定Session域屬性。
除此之外,還需要修改services.xml檔案的内容,為<service>元素加一個scope屬性,該屬性有四個可取的值:Application, SOAPSession, TransportSession, Request,不過要注意一下,雖然Axis2的官方文檔将這四個值的單詞首字母和縮寫字母都寫成了大寫,但經筆者測試,必須全部小寫才有效,也就是這四個值應為:application、soapsession、transportsession、request,其中request為scope屬性的預設值。讀者可以選擇使用transportsession和application分别實作同一個WebService類和跨WebService類的會話管理。
在用戶端需要使用setManageSession(true)打開Session管理功能。
綜上所述,實作同一個WebService的Session管理需要如下三步:
1. 使用MessageContext和ServiceContext獲得與設定key-value對。
2. 為要進行Session管理的WebService類所對應的<service>元素添加一個scope屬性,并将該屬性值設為transportsession。
3. 在用戶端使用setManageSession(true)打開Session管理功能。
下面是一個在同一個WebService類中管理Session的例子。
先建立一個WebService類,代碼如下:
package service;
import org.apache.axis2.context.ServiceContext;
import org.apache.axis2.context.MessageContext;
public class LoginService
{
public boolean login(String username, String password)
{
if("bill".equals(username) && "1234".equals(password))
{
// 第1步:設定key-value對
MessageContext mc = MessageContext.getCurrentMessageContext();
ServiceContext sc = mc.getServiceContext();
sc.setProperty("login", "成功登入");
return true;
}
else
{
return false;
}
}
public String getLoginMsg()
{
// 第1步:獲得key-value對中的value
MessageContext mc = MessageContext.getCurrentMessageContext();
ServiceContext sc = mc.getServiceContext();
return (String)sc.getProperty("login");
}
}
在LoginService類中有兩個方法:login和getLoginMsg,如果login方法登入成功,會将“成功登入”字元串儲存在ServiceContext對象中。如果在login方法傳回true後調用getLoginMsg方法,就會傳回“成功登入”。
下面是LoginService類的配置代碼(services.xml):
<!-- 第2步:添加scope屬性 -->
< service name ="loginService" scope ="transportsession" >
< description >
登入服務
</ description >
< parameter name ="ServiceClass" >
service.LoginService
</ parameter >
< messageReceivers >
< messageReceiver mep ="http://www.w3.org/2004/08/wsdl/in-out"
class ="org.apache.axis2.rpc.receivers.RPCMessageReceiver" />
</ messageReceivers >
</ service >
使用如下的指令生成用戶端使用的stub類:
%AXIS2_HOME%\bin\wsdl2java -uri http://localhost:8080/axis2/services/loginService?wsdl -p client -s -o stub
在stub\src\client目錄中生成了一個LoginServiceStub.java類,在該類中找到如下的構造句方法:
public LoginServiceStub(org.apache.axis2.context.ConfigurationContext configurationContext,
java.lang.String targetEndpoint, boolean useSeparateListener)
throws org.apache.axis2.AxisFault
{
_serviceClient.getOptions().setSoapVersionURI(
org.apache.axiom.soap.SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI);
}
在該方法中最後添加如下的代碼:
// 第3步:打開用戶端的Session管理功能
_serviceClient.getOptions().setManageSession( true);
下面的用戶端代碼使用LoginServiceStub對象通路了剛才建立的WebService:
LoginServiceStub stub = new LoginServiceStub();
LoginServiceStub.Login login = new LoginServiceStub.Login();
login.setUsername("bill");
login.setPassword("1234");
if(stub.login(login).local_return)
{
System.out.println(stub.getLoginMsg().local_return);
}
在《WebService大講堂之Axis2(5):會話(Session)管理》一文中介紹了如何使用Axis2來管理同一個服務的會話,但對于一個複雜的系統,不可能隻有一個WebService服務,例如,至少會有一個管理使用者的WebService(使用者登入和注冊)以及處理業務的WebService。象這種情況,就必須在多個WebService服務之間共享會話狀态,也稱為跨服務會話(Session)管理。實作跨服務會話管理與實作同一個服務的會話管理的步驟類似,但仍然有一些差别,實作跨服務會話管理的步驟如下:
實作跨服務的Session管理需要如下三步:
1. 使用MessageContext和ServiceGroupContext獲得與設定key-value對。
2. 為要進行Session管理的WebService類所對應的<service>元素添加一個scope屬性,并将該屬性值設為application。
3. 在用戶端使用setManageSession(true)打開Session管理功能。
從上面的步驟可以看出,實作跨服務會話管理與實作同一個服務的會話管理在前兩步上存在着差異,而第3步是完全一樣的。下面是一個跨服務的會話管理的執行個體。在這個例子中有兩個WebService類:LoginService和SearchService,代碼如下:
LoginService.java
package service;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.context.ServiceGroupContext;
public class LoginService
{
public boolean login(String username, String password)
{
if("bill".equals(username) && "1234".equals(password))
{
// 第1步:設定key-value對
MessageContext mc = MessageContext.getCurrentMessageContext();
ServiceGroupContext sgc = mc.getServiceGroupContext();
sgc.setProperty("login", "成功登入");
return true;
}
else
{
return false;
}
}
public String getLoginMsg()
{
// 第1步:獲得key-value對中的value
MessageContext mc = MessageContext.getCurrentMessageContext();
ServiceGroupContext sgc = mc.getServiceGroupContext();
return (String)sgc.getProperty("login");
}
}
SearchService.java
package service;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.context.ServiceGroupContext;
public class SearchService
{
public String findByName(String name)
{
// 第1步:獲得key-value對中的value
MessageContext mc = MessageContext.getCurrentMessageContext();
ServiceGroupContext sgc = mc.getServiceGroupContext();
if (sgc.getProperty("login") != null)
return "找到的資料<" + name + ">";
else
return "使用者未登入";
}
}
services.xml檔案中的配置代碼如下:
< serviceGroup >
<!-- 第2步:添加scope屬性,并設定屬性值為application -->
< service name ="loginService" scope ="application" >
< description >
登入服務
</ description >
< parameter name ="ServiceClass" >
service.LoginService
</ parameter >
< messageReceivers >
< messageReceiver mep ="http://www.w3.org/2004/08/wsdl/in-out"
class ="org.apache.axis2.rpc.receivers.RPCMessageReceiver" />
</ messageReceivers >
</ service >
<!-- 第2步:添加scope屬性,并設定屬性值為application -->
< service name ="searchService" scope ="application" >
< description >
搜尋服務
</ description >
< parameter name ="ServiceClass" >
service.SearchService
</ parameter >
< messageReceivers >
< messageReceiver mep ="http://www.w3.org/2004/08/wsdl/in-out"
class ="org.apache.axis2.rpc.receivers.RPCMessageReceiver" />
</ messageReceivers >
</ service >
</ serviceGroup >
第3步與《WebService大講堂之Axis2(5):會話(Session)管理》一文中介紹的方法類似。
下面是使用兩個stub類的對象執行個體通路上面實作的兩個WebService的用戶端代碼:
LoginServiceStub stub = new LoginServiceStub();
LoginServiceStub.Login login = new LoginServiceStub.Login();
login.setUsername("bill");
login.setPassword("1234");
if(stub.login(login).local_return)
{
System.out.println(stub.getLoginMsg().local_return);
SearchServiceStub searchStub = new SearchServiceStub();
SearchServiceStub.FindByName fbn = new SearchServiceStub.FindByName();
fbn.setName("abc");
System.out.println(searchStub.findByName(fbn).local_return);
}
在執行上面的代碼後,将輸出如下的資訊:
成功登入
找到的資料<abc>
讀者可以将scope屬性值改成transportsession,看看會輸出什麼!
實際上,Axis2的會話管理也是通過Cookie實作的,與Web應用中的Session管理類似。如果讀者使用C#通路支援會話(在同一個服務中的會話管理)的WebService,需要指定一個CookieContainer對象,代碼如下:
service.loginService ls = new service.loginService();
System.Net.CookieContainer cc = new System.Net.CookieContainer();
ls.CookieContainer = cc;
bool r, rs;
ls.login("bill", "1234", out @r, out rs);
if (r)
{
MessageBox.Show(ls.getLoginMsg()[email protected]);
}
如果是通路跨服務的支援會話的WebService,則不需要指定CookieContainer對象,代碼如下:
service.loginService ls = new service.loginService();
bool r, rs;
ls.login("bill", "1234", out @r, out rs);
if (r)
{
service1.searchService ss = new service1.searchService();
MessageBox.Show(ss.findByName("abc"));
}
在現今的Web應用中經常使用Spring架構來裝載JavaBean。如果要想将某些在Spring中裝配的JavaBean釋出成WebService,使用Axis2的Spring感覺功能是非常容易做到的。
在本文的例子中,除了<Tomcat安裝目錄>\webapps\axis2目錄及該目錄中的相關庫外,還需要Spring架構中的spring.jar檔案,将該檔案複制到<Tomcat安裝目錄>\webapps\axis2\WEB-INF\lib目錄中。
下面先建立一個JavaBean(該JavaBean最終要被釋出成WebService),代碼如下:
package service;
import entity.Person;
public class SpringService
{
private String name;
private String job;
public void setName(String name)
{
this.name = name;
}
public void setJob(String job)
{
this.job = job;
}
public Person getPerson()
{
Person person = new Person();
person.setName(name);
person.setJob(job);
return person;
}
public String getGreeting(String name)
{
return "hello " + name;
}
}
其中Person也是一個JavaBean,代碼如下:
package entity;
public class Person
{
private String name;
private String job;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getJob()
{
return job;
}
public void setJob(String job)
{
this.job = job;
}
}
将上面兩個Java源檔案編譯後,放到<Tomcat安裝目錄>\webapps\axis2\WEB-INF\classes目錄中。
在<Tomcat安裝目錄>\webapps\axis2\WEB-INF\web.xml檔案中加入下面的内容:
< listener >
< listener-class >org.springframework.web.context.ContextLoaderListener </ listener-class >
</ listener >
< context-param >
< param-name >contextConfigLocation </ param-name >
< param-value >/WEB-INF/applicationContext.xml </ param-value >
</ context-param >
在<Tomcat安裝目錄>\webapps\axis2\WEB-INF目錄中建立一個applicationContext.xml檔案,該檔案是Spring架構用于裝配JavaBean的配置檔案,内容如下:
<? xml version="1.0" encoding="UTF-8" ?>
< beans xmlns ="http://www.springframework.org/schema/beans"
xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop ="http://www.springframework.org/schema/aop"
xmlns:tx ="http://www.springframework.org/schema/tx"
xsi:schemaLocation ="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd" >
< bean id ="springService" class ="service.SpringService" >
< property name ="name" value ="姚明" />
< property name ="job" value ="職業男籃" />
</ bean >
</ beans >
在applicationContext.xml檔案中裝配了service.SpringService類,并被始化了name和job屬性。在配置完SpringService類後,就可以直接在程式中FileSystemXmlApplicationContext類或其他類似功能的類讀取applicationContext.xml檔案中的内容,并獲得SpringService類的對象執行個體。但現在我們并不這樣做,而是将SpringService類釋出成WebService。
在<Tomcat安裝目錄>\webapps\axis2\WEB-INF\lib目錄中有一個axis2-spring-1.4.1.jar檔案,該檔案用于将被裝配JavaBean的釋出成WebService。在D盤建立一個axi2-spring-ws目錄,并在該目錄中建立一個META-INF子目錄。在META-INF目錄中建立一個services.xml檔案,内容如下:
< service name ="springService" >
< description >
Spring aware
</ description >
< parameter name ="ServiceObjectSupplier" >
org.apache.axis2.extensions.spring.receivers.SpringServletContextObjectSupplier
</ parameter >
< parameter name ="SpringBeanName" >
springService
</ parameter >
< messageReceivers >
< messageReceiver mep ="http://www.w3.org/2004/08/wsdl/in-out"
class ="org.apache.axis2.rpc.receivers.RPCMessageReceiver" />
</ messageReceivers >
</ service >
在Windows控制台進入axi2-spring-ws目錄,并使用jar指令将axi2-spring-ws目錄中的内容打包成axi2-spring-ws.aar,然後将該檔案複制到<Tomcat安裝目錄>\webapps\axis2\WEB-INF\services目錄中,啟動Tomcat後,就可以通路該WebService了,通路方式與前面幾篇文章的通路方式相同。獲得wsdl内容的URL如下:
http://localhost:8080/axis2/services/springService?wsdl
在将Spring中的裝配JavaBean釋出成WebService需要注意以下幾點:
1. 由JavaBean編譯生成的.class檔案需要放在WEB-INF\classes目錄中,或打成.jar包後放在WEB-INF\lib目錄中,而WEB-INF\services目錄中的.aar包中不需要包含.class檔案,而隻需要包含一個META-INF目錄,并在該目錄中包含一個services.xml檔案即可。
2. services.xml的配置方法與前幾篇文章的配置方法類似,隻是并不需要使用ServiceClass參數指定要釋出成WebService的Java類,而是要指定在applicationContext.xml檔案中的裝配JavaBean的名稱(SpringBeanName參數)。
3. 在services.xml檔案中需要通過ServiceObjectSupplier參數指定SpringServletContextObjectSupplier類來獲得Spring的ApplicationContext對象。
前面幾篇文章中都是使用同步方式來調用WebService。也就是說,如果被調用的WebService方法長時間不傳回,用戶端将一直被阻塞,直到該方法傳回為止。使用同步方法來調用WebService雖然很直覺,但當WebService方法由于各種原因需要很長時間才能傳回的話,就會使用戶端程式一直處于等待狀态,這樣使用者是無法忍受的。
當然,我們很容易就可以想到解決問題的方法,這就是多線程。解決問題的基本方法是将通路WebService的任務交由一個或多個線程來完成,而主線程并不負責通路WebService。這樣即使被通路的WebService方法長時間不傳回,用戶端仍然可以做其他的工作。我們可以管這種通過多線程通路WebService的方式稱為異步通路。
雖然直接使用多線程可以很好地解決這個問題,但比較麻煩。幸好Axis2的用戶端提供了異步通路WebService的功能。
RPCServiceClient類提供了一個invokeNonBlocking方法可以通過異步的方式來通路WebService。下面先來建立一個WebService。
MyService是一個WebService類,代碼如下:
package service;
public class MyService
{
public String getName()
{
try
{
System.out.println("getName方法正在執行
");
// 延遲5秒
Thread.sleep(5000);
}
catch (Exception e)
{
}
return "火星";
}
}
為了模拟需要一定時間才傳回的WebService方法,在getName方法中使用了sleep方法來延遲5秒。
下面是MyService類的配置代碼:
<!-- services.xml -->
< service name ="myService" >
< description >
異步調用示範
</ description >
< parameter name ="ServiceClass" >
service.MyService
</ parameter >
< messageReceivers >
< messageReceiver mep ="http://www.w3.org/2004/08/wsdl/in-out"
class ="org.apache.axis2.rpc.receivers.RPCMessageReceiver" />
</ messageReceivers >
</ service >
從上面的配置代碼可以看出,MyService的配置方式與前幾章的WebService的配置方式完全一樣,也就是說,MyService隻是一個普通的WebService。
下面是異步調用MyService的Java用戶端代碼:
package client;
import javax.xml.namespace.QName;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.Options;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.rpc.client.RPCServiceClient;
public class RPCAsyncClient
{
public static void main(String[] args) throws Exception
{
RPCServiceClient serviceClient = new RPCServiceClient();
Options options = serviceClient.getOptions();
EndpointReference targetEPR = new EndpointReference(
"http://localhost:8080/axis2/services/myService");
options.setTo(targetEPR);
Object[] opAddEntryArgs = new Object[]{};
QName opAddEntry = new QName("http://service", "getName");
serviceClient.invokeNonBlocking(opAddEntry, opAddEntryArgs,
new org.apache.axis2.client.async.AxisCallback()
{
@Override
public void onComplete()
{
}
@Override
public void onError(Exception arg0)
{
} }
@Override
public void onFault(MessageContext arg0)
{
}
@Override
public void onMessage(MessageContext mc)
{
// 輸出傳回值
System.out.println(mc.getEnvelope().getFirstElement()
.getFirstElement().getFirstElement().getText());
}
});
System.out.println("異步調用!");
// 阻止程式退出
System.in.read();
}
}
從上面的代碼可以看出,invokeNonBlocking方法有三個參數,前兩個參數分别指定了要調用的方法及方法參數的相關資訊,而最後一個參數并不是方法傳回值的類型資訊,而是一個實作org.apache.axis2.client.async.AxisCallback接口的類的對象執行個體。在本例中隐式實作了AxisCallback接口。在AxisCallback接口中有四個方法需要實作,其中當被異步調用的方法傳回時onMessage方法被調用。當運作上面的程式後,将輸出如下的資訊:
異步調用!
火星
雖然上面的例子可以實作異步調用,但比較麻煩。為了更友善地實作異步調用,可以使用wsdl2java指令的-a參數生成可異步調用的Stub類。下面的指令可生成同步和異步調用的用戶端代碼(兩個類),其中-s表示生成同步調用代碼,-a表示生成異步調用代碼。
%AXIS2_HOME%\bin\wsdl2java -uri http://localhost:8080/axis2/services/myService?wsdl -p client -s -a -o stub
在執行上面的指令後,将生成兩個類:MyServiceStub和MyServiceCallbackHandler類,其中MyServiceStub類負責同步和異步調用WebService,MyServiceCallbackHandler類是一個抽象類,也是一個回調類,當使用異步方式調用WebService方法時,如果方法傳回,則MyServiceCallbackHandler類的receiveResultgetName方法被調用。下面是使用MyServiceStub類異步通路WebService的代碼:
package client;
import client.MyServiceStub.GetNameResponse;
class MyCallback extends MyServiceCallbackHandler
{
@Override
public void receiveResultgetName(GetNameResponse result)
{
// 輸出getName方法的傳回結果
System.out.println(result.get_return());
}
}
public class StubClient
{
public static void main(String[] args) throws Exception
{
MyServiceStub stub = new MyServiceStub();
// 異步調用WebService
stub.startgetName( new MyCallback());
System.out.println("異步調用!");
System.in.read();
}
}
執行上面的程式後,将輸出如下的資訊:
異步調用!
火星
在.net中也可以使用異步的方式來調用WebService,如在C#中可使用如下的代碼來異步調用getName方法:
// 回調方法
private void getNameCompletedEvent(object sender, WSC.asyn.getNameCompletedEventArgs e)
{
listBox1.Items.Add( e.Result.@ return);
}
private void button1_Click(object sender, EventArgs e)
{
async.myService my = new WSC.async.myService();
my.getNameCompleted += new WSC.async.getNameCompletedEventHandler(getNameCompletedEvent);
my.getNameAsync();
MessageBox.Show("完成調用");
}
其中async是引用MyService的服務名。要注意的是,在C#中不能在同一個WebService執行個體的getName方法未傳回之前,再次調用該執行個體的getName方法,否則将抛出異常。如下面的代碼會抛出一個異常:
async.myService my = new WSC.async.myService();
my.getNameCompleted += new WSC.async.getNameCompletedEventHandler(getNameCompletedEvent);
my.getNameAsync();
// 将抛出異常
my.getNameAsync();
但不同的WebService執行個體的方法可以在方法未傳回時調用,如下面的代碼是可以正常工作的:
asyn.myService my = new WSC.asyn.myService();
my.getNameAsync();
my.getNameCompleted += new WSC.asyn.getNameCompletedEventHandler(getNameCompletedEvent);
asyn.myService my1 = new WSC.asyn.myService();
my1.getNameCompleted += new WSC.asyn.getNameCompletedEventHandler(getNameCompletedEvent);
my1.getNameAsync();
Axis2可以通過子產品(Module)進行擴充。Axis2子產品至少需要有兩個類,這兩個類分别實作了Module和Handler接口。開發和使用一個Axis2子產品的步驟如下:
1. 編寫實作Module接口的類。Axis2子產品在進行初始化、銷毀等動作時會調用該類中相應的方法)。
2. 編寫實作Handler接口的類。該類是Axis2子產品的業務處理類。
3. 編寫module.xml檔案。該檔案放在META-INF目錄中,用于配置Axis2子產品。
4. 在axis2.xml檔案中配置Axis2子產品。
5. 在services.xml檔案中配置Axis2子產品。每一個Axis2子產品都需要使用<module>元素引用才能使用。
6. 釋出Axis2子產品。需要使用jar指令将Axis2子產品壓縮成.mar包(檔案擴充名必須是.mar),然後将.mar檔案放在
<Tomcat安裝目錄>\webapps\axis2\WEB-INF\modules目錄中。
先來編寫一個WebService類,代碼如下:
package service;
public class MyService
{
public String getGreeting(String name)
{
return "您好 " + name;
}
}
下面我們來編寫一個記錄請求和響應SOAP消息的Axis2子產品。當用戶端調用WebService方法時,該Axis2子產品會将請求和響應SOAP消息輸出到Tomcat控制台上。
第1步:編寫LoggingModule類
LoggingModule類實作了Module接口,代碼如下:
package module;
import org.apache.axis2.AxisFault;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.description.AxisDescription;
import org.apache.axis2.description.AxisModule;
import org.apache.axis2.modules.Module;
import org.apache.neethi.Assertion;
import org.apache.neethi.Policy;
public class LoggingModule implements Module
{
// initialize the module
public void init(ConfigurationContext configContext, AxisModule module)
throws AxisFault
{
System.out.println("init");
}
public void engageNotify(AxisDescription axisDescription) throws AxisFault
{
}
// shutdown the module
public void shutdown(ConfigurationContext configurationContext)
throws AxisFault
{
System.out.println("shutdown");
}
public String[] getPolicyNamespaces()
{
return null;
}
public void applyPolicy(Policy policy, AxisDescription axisDescription)
throws AxisFault
{
}
public boolean canSupportAssertion(Assertion assertion)
{
return true;
}
}
在本例中LoggingModule類并沒實作實際的功能,但該類必須存在。當Tomcat啟動時會裝載該Axis2子產品,同時會調用LoggingModule類的init方法,并在Tomcat控制台中輸出“init”。
第2步:編寫LogHandler類
LogHandler類實作了Handler接口,代碼如下:
package module;
import org.apache.axis2.AxisFault;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.engine.Handler;
import org.apache.axis2.handlers.AbstractHandler;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class LogHandler extends AbstractHandler implements Handler
{
private static final Log log = LogFactory.getLog(LogHandler. class);
private String name;
public String getName()
{
return name;
}
public InvocationResponse invoke(MessageContext msgContext)
throws AxisFault
{
// 向Tomcat控制台輸出請求和響應SOAP消息
log.info(msgContext.getEnvelope().toString());
return InvocationResponse.CONTINUE;
}
public void revoke(MessageContext msgContext)
{
log.info(msgContext.getEnvelope().toString());
}
public void setName(String name)
{
this.name = name;
}
}
LogHandler類的核心方法是invoke,當使用該Axis2子產品的WebService的方法被調用時,LogHandler類的invoke方法被調用。
第3步:編寫module.xml檔案
在META-INF目錄中建立一個module.xml檔案,内容如下:
< module name ="logging" class ="module.LoggingModule" >
< InFlow >
< handler name ="InFlowLogHandler" class ="module.LogHandler" >
< order phase ="loggingPhase" />
</ handler >
</ InFlow >
< OutFlow >
< handler name ="OutFlowLogHandler" class ="module.LogHandler" >
< order phase ="loggingPhase" />
</ handler >
</ OutFlow >
< OutFaultFlow >
< handler name ="FaultOutFlowLogHandler" class ="module.LogHandler" >
< order phase ="loggingPhase" />
</ handler >
</ OutFaultFlow >
< InFaultFlow >
< handler name ="FaultInFlowLogHandler" class ="module.LogHandler" >
< order phase ="loggingPhase" />
</ handler >
</ InFaultFlow >
</ module >
第4步:在axis2.xml檔案中配置Axis2子產品
打開axis2.xml檔案,分别在如下四個<phaseOrder>元素中加入<phase name="loggingPhase"/>:
< phaseOrder type ="InFlow" >
< phase name ="soapmonitorPhase" />
< phase name ="loggingPhase" />
</ phaseOrder >
< phaseOrder type ="OutFlow" >
< phase name ="Security" />
< phase name ="loggingPhase" />
</ phaseOrder >
< phaseOrder type ="InFaultFlow" >
< phase name ="soapmonitorPhase" />
< phase name ="loggingPhase" />
</ phaseOrder >
< phaseOrder type ="OutFaultFlow" >
< phase name ="Security" />
< phase name ="loggingPhase" />
</ phaseOrder >
第5步:在services.xml檔案中引用logging子產品
services.xml檔案的内容如下:
< service name ="myService" >
< description >
使用logging子產品
</ description >
<!-- 引用logging子產品 -->
< module ref ="logging" />
< parameter name ="ServiceClass" >
service.MyService
</ parameter >
< messageReceivers >
< messageReceiver mep ="http://www.w3.org/2004/08/wsdl/in-out"
class ="org.apache.axis2.rpc.receivers.RPCMessageReceiver" />
</ messageReceivers >
</ service >
第6步:釋出logging子產品
到現在為止,我們應用可以建立兩個發行包:logging.mar和service.aar。其中logging.mar檔案是Axis2子產品的發行包,該包的目錄結構如下:
logging.mar
module\LoggingModule.class
module\LogHandler.class
META-INF\module.xml
service.aar檔案是本例編寫的WebService發行包,該包的目錄結構如下:
service.aar
service\MyService.class
META-INF\services.xml
将logging.mar檔案放在<Tomcat安裝目錄>\webapps\axis2\WEB-INF\modules目錄中,将service.aar檔案放在<Tomcat安裝目錄>\webapps\axis2\WEB-INF\services目錄中。要注意的是,如果modules目錄中包含了modules.list檔案,Axis2會隻裝載在該檔案中引用的Axis2子產品,是以,必須在該檔案中引用logging子產品,該檔案的内容如下:
addressing-1.4.1.mar
soapmonitor-1.4.1.mar
ping-1.4.1.mar
mex-1.4.1.mar
axis2-scripting-1.4.1.mar
logging.mar
如果modules目錄中不包含modules.list檔案,則Axis2會裝載modules檔案中的所有Axis2子產品。
現在啟動Tomcat,使用如下的C#代碼調用MyService的getGreeting方法則會在Tomcat控制台中輸出相應的請求和響應SOAP消息。
// async是引用MyService的服務名
async.myService my = new WSC.asyn.myService();
MessageBox.Show(my.getGreeting("中國"));
MessageBox.Show("完成調用");
在執行上面的代碼後,在Tomcat控制台中輸出的資訊如下圖所示。
在Axis2中提供了一個Axis2子產品(soapmonitor),該子產品實作了與《WebService大講堂之Axis2(9):編寫Axis2子產品(Module)》中實作的logging子產品相同的功能,所不同的是,logging子產品直接将SOAP請求與響應消息輸出到Tomcat控制台中,而soapmonitor子產品利用applet直接在頁面中輸出SOAP請求和響應消息。
下面是配置和使用soapmonitor子產品的步驟:
第1步:部署Applet和Servlet
由于axis2預設情況下已經自帶了soapmonitor子產品,是以,soapmonitor子產品并不需要單獨安裝。但applet所涉及到的相應的.class檔案需要安裝一下。在<Tomcat安裝目錄>\webapps\axis2\WEB-INF\lib目錄中找到soapmonitor-1.4.1.jar檔案,将該檔案解壓。雖然applet并不需要soapmonitor-1.4.1.jar檔案中所有的.class檔案,但為了友善,讀者也可以直接将解壓目錄中的org目錄複制到<Tomcat安裝目錄>\webapps\axis2目錄中,Applet所需的.class檔案需要放在這個目錄。然後再将org目錄複制到<Tomcat安裝目錄>\webapps\axis2\WEB-INF\classes目錄中,soapmonitor子產品中的Servlet所對應的.class檔案需要放在這個目錄。
第2步:配置Servlet
打開<Tomcat安裝目錄>\webapps\axis2\WEB-INF\web.xml檔案,在其中加入如下的内容:
< servlet >
< servlet-name >SOAPMonitorService </ servlet-name >
< servlet-class >
org.apache.axis2.soapmonitor.servlet.SOAPMonitorService
</ servlet-class >
< init-param >
< param-name >SOAPMonitorPort </ param-name >
< param-value >5001 </ param-value >
</ init-param >
< load-on-startup >1 </ load-on-startup >
</ servlet >
< servlet-mapping >
< servlet-name >SOAPMonitorService </ servlet-name >
< url-pattern >/SOAPMonitor </ url-pattern >
</ servlet-mapping >
第3步:在services.xml檔案中引用soapmonitor子產品
與引用logging子產品一樣,引用soapmonitor子產品也需要使用<module>元素,引用soapmonitor子產品的services.xml檔案的内容如下:
< service name ="myService" >
< description >
使用logging和soapmonitor子產品
</ description >
<!-- 引用logging子產品 -->
< module ref ="logging" />
<!-- 引用soapmonitor子產品 -->
< module ref ="soapmonitor" />
< parameter name ="ServiceClass" >
service.MyService
</ parameter >
< messageReceivers >
< messageReceiver mep ="http://www.w3.org/2004/08/wsdl/in-out"
class ="org.apache.axis2.rpc.receivers.RPCMessageReceiver" />
</ messageReceivers >
</ service >
由于soapmonitor子產品已經在axis2.xml進行配置了,是以,在本例中不需要再對axis2.xml檔案進行配置了。
第4步:使用soapmonitor子產品
啟動Tomcat後,在浏覽器中輸入如下的URL:
http://localhost:8080/axis2/SOAPMonitor
在浏覽器中将出現soapmonitor所帶的Applet的界面,當通路MyService的getGreeting方法時,在Tomcat控制台與Applet中都顯示了相應的SOAP請求和響應消息。如圖1和圖2分别是調用了兩次getGreeting方法後輸出的SOAP請求和響應消息。
圖1
圖2
如果讀者想讓logging和soapmonitor子產品監視部署在Axis2中的所有WebService,可以在axis2.xml檔案中使用<module>元素來引用這兩個子產品,代碼如下:
<!-- 引用logging子產品 -->
< module ref ="logging" />
<!-- 引用soapmonitor子產品 -->
< module ref ="soapmonitor" />