當我們的 Web Service 有傳遞檔案的需求時,除了使用 byte 陣列之外,還可以使用 javax.activation.DataHandler 這個類別。
要使用這個類別,尚需引入 activation.jar (JAF) 與 mail.jar 這兩個套件。
這裏要注意的是,有些教學網站只教你去 SUN 網站下載 JAF 來使用即可,但其實 JAF 就是我們使用 java mail 時需要引入的 activation.jar 檔,不過這也還好,主要是這些網站並沒說要引入 mail.jar,結果就是在執行時會丟出『java.lang.RuntimeException: No support for attachments』的錯誤,當時的確是造成了笨鳥的困擾,好加在有在別的網站找到原因,不然又不知要被困多久了 ~~ >_<
言歸正傳,由於 javax.activation.DataHandler 類別並不是 Web Service 傳遞協定的標準,因此如果我們像前一篇「利用 Eclipse 簡單建立 AXIS 的 WebService」裏寫的那樣直接用 Eclipse 來幫我們產生並註冊 Web Service 的話,在過程中很有可能會有錯誤的產生(IWAB0399E Error in generating Java from WSDL: java.io.IOException: Type {http://activation.javax}DataHandler is referenced but not defined.
) 而導致無法完成部署,所以我們必需自行建置/修改部署檔的內容,並手動進行部署,至於如何手動部署已在前一篇講解過了,此處就不再解講。
首先,我們先寫一個傳送/接收 DataHandler 的 Web Service
public class DataHandlerWS1 { /** * 接收一個 Datahandler,並將其中夾帶的檔案存到磁碟 */ public void receive(DataHandler dh, String name) { try { copyStream(dh, name); } catch (Exception e) { e.printStackTrace(); } } /** * 讀取磁碟中的一個檔案回傳 */ public DataHandler send() { DataHandler dh = null; try { dh = new DataHandler((new File("E:\\\\MSN\\onion14.gif")).toURL()); } catch (Exception e) { e.printStackTrace(); } return dh; } /** * 將 DataHandler 寫出成檔案,此方法不發佈成 Web Service。 */ private void copyStream(DataHandler dh, String name) throws Exception { BufferedInputStream bis = null; BufferedOutputStream bos = null; try { bis = new BufferedInputStream(dh.getInputStream()); bos = new BufferedOutputStream( new FileOutputStream(new File("C:\\tmp\\axis\\" + name))); byte[] buffer = new byte[1024 * 4]; int n = 0; while ((n = bis.read(buffer)) != -1) { bos.write(buffer, 0, n); } } finally { if (bis != null) { bis.close(); } if (bos != null) { bos.close(); } } } }
上面的 Web Service 只是一個很簡單的 Java 程式,receive() 接收由呼叫端傳來的 DataHandler 物件,並將其依傳入的檔名存入磁碟中;而 send() 則是從磁碟中取出一個檔案包在 DataHandler 物件後,傳回給呼叫端。
接著撰寫部署用的 wsdd 檔。
wsdd 檔有兩種寫法,一種是特別去宣告 DataHandler 這個類別,並指定為其序列與反序列化的類別;而另一種則是將 DataHandler 以標準的 base64Binary 來替代。以下會列出這兩種寫法。
.特別宣告 DataHandler 的寫法
宣告是為了讓 Server 在傳出 / 收到 DataHandler 類別時,知道該如何去序列與反序列化。
<?xml version="1.0" encoding="UTF-8"?> <deployment xmlns = "http://xml.apache.org/axis/wsdd/" xmlns:java = "http://xml.apache.org/axis/wsdd/providers/java" xmlns:ns1 = "http://axis.teach" xmlns:tns = "http://www.w3.org/2001/XMLSchema" xmlns:dh = "http://activation.javax"> <!-- 定義 Web Service --> <service name="DataHandlerWS1" provider="java:RPC" style="wrapped" use="literal"> <!-- 此 Web Service 指向的類別全名--> <parameter name="className" value="teach.axis.DataHandlerWS1"/> <!-- 允許開放呼叫的方法名,有多個用空格隔開,若要全開放則填上 * --> <parameter name="allowedMethods" value="send receive"/> <!-- 宣告方法:send 方法有回傳值,此處亦指定其回傳型態--> <operation name="send" qname="ns1:send" returnQName="ns1:sendReturn" returnType="dh:DataHandler"> </operation> <!-- 宣告方法:receive 方法有兩個傳入值,此處會去宣告此兩個參數值的型態--> <operation name="receive" qname="ns1:receive"> <parameter name="dh" qname="ns1:dh" type="dh:DataHandler"/> <parameter name="name" qname="ns1:name" type="tns:string"/> </operation> <!-- 宣告 DataHandler 這個類別,包括指向的類別與序列/反序列化的類別等--> <typeMapping qname="dh:DataHandler" languageSpecificType="java:javax.activation.DataHandler" serializer="org.apache.axis.encoding.ser.JAFDataHandlerSerializerFactory" deserializer="org.apache.axis.encoding.ser.JAFDataHandlerDeserializerFactory" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" /> </service> </deployment>
上面的 wsdd 檔中,24 ~ 28 行是宣告 javax.activation.DataHandler 這個類別型態,而 16 與 20 行則是使用了宣告的 QName 來指名回傳與接收的是 DataHandler 物件。
.使用 base64Binary 的寫法
<?xml version="1.0" encoding="UTF-8"?>
<deployment xmlns = "http://xml.apache.org/axis/wsdd/"
xmlns:java = "http://xml.apache.org/axis/wsdd/providers/java"
xmlns:ns1 = "http://axis.teach"
xmlns:tns = "http://www.w3.org/2001/XMLSchema"
xmlns:dh = "http://activation.javax">
<!-- 定義 Web Service -->
<service name="DataHandlerWS1_DP2" provider="java:RPC" style="wrapped" use="literal">
<!-- 此 Web Service 指向的類別全名-->
<parameter name="className" value="teach.axis.DataHandlerWS1"/>
<!-- 允許開放呼叫的方法名,有多個用空格隔開,若要全開放則填上 * -->
<parameter name="allowedMethods" value="send receive"/>
<!-- 宣告方法:send 方法有回傳值,此處亦指定其回傳型態-->
<operation name="send" qname="ns1:send"
returnQName="ns1:sendReturn"
returnType="tns:base64Binary">
</operation>
<!-- 宣告方法:receive 方法有兩個傳入值,此處會去宣告此兩個參數值的型態-->
<operation name="receive" qname="ns1:receive">
<parameter name="dh" qname="ns1:dh" type="tns:base64Binary"/>
<parameter name="name" qname="ns1:name" type="tns:string"/>
</operation>
</service>
</deployment>
請看第 16 與 20 行,跟前一個 wsdd 檔比較,原本傳入與傳出的是 DataHandler 類別,但此處我們使用標準的 base64Binary 來替代。
部署用的 wsdd 檔完成後,依序執行上面兩個 wsdd 進行部署 (執行 wsdd 的步驟請參考上一篇文章),若部署成功則現在 Server 上會有 DataHandlerWS1 (使用宣告 DataHandler 的方式) 與 DataHandlerWS1_DP2 (使用 base64Binary) 兩支 Web Service。
部署完成後就可以寫 Client 端程式來試試看了。
package teach.axis.client; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import javax.activation.DataHandler; import javax.xml.namespace.QName; import javax.xml.rpc.ParameterMode; import javax.xml.rpc.ServiceException; import javax.xml.rpc.encoding.XMLType; import org.apache.axis.client.Call; import org.apache.axis.client.Service; import org.apache.axis.encoding.ser.JAFDataHandlerDeserializerFactory; import org.apache.axis.encoding.ser.JAFDataHandlerSerializerFactory; public class DataHandlerWS1Client { public static final String url = "http://localhost:8080/AxisTeach/services/DataHandlerWS1"; public static final QName dhQName = new QName("http://activation.javax", "DataHandler"); /** * 建立呼叫 Web Service 的 Call 物件 */ private static Call createCall() throws ServiceException { Service service = new Service(); Call call = (Call) service.createCall(); call.setTargetEndpointAddress(url); // 由於使用了非 WebService 標準的 DataHandler,因此要註冊。 call.registerTypeMapping( DataHandler.class, dhQName, JAFDataHandlerSerializerFactory.class, JAFDataHandlerDeserializerFactory.class); return call; } /** * 測試 DataHandlerWS1 的 receive() 方法 */ public static void callReceive(Call call) throws Exception { // 清空前一次 Call 的呼叫所設定的參數 call.clearOperation(); // 傳入參數也可寫成 new QName("receive") call.setOperationName(new QName(url, "receive")); // 設定傳入參數的型態(QName) call.addParameter("dh", dhQName, ParameterMode.IN); call.addParameter("name", XMLType.XSD_STRING, ParameterMode.IN); DataHandler dh = new DataHandler((new File("E:\\\\MSN\\em184.gif")).toURL()); call.invoke(new Object[]{dh, "toWS.gif"}); } /** * 測試 DataHandlerWS1 的 send() 方法 */ public static void callSend(Call call) throws Exception { // 清空前一次 Call 的呼叫所設定的參數 call.clearOperation(); // 傳入參數也可寫成 new QName("send") call.setOperationName(new QName(url, "send")); // 宣告回傳的類型 call.setReturnClass(DataHandler.class); DataHandler dh = (DataHandler) call.invoke((Object[]) null); copyStream(dh, "fromWS.gif"); } /** * 將 DataHandler 內的 input stream 存成檔案。 */ private static void copyStream(DataHandler dh, String name) throws Exception { BufferedInputStream bis = null; BufferedOutputStream bos = null; try { bis = new BufferedInputStream(dh.getInputStream()); bos = new BufferedOutputStream(new FileOutputStream(new File("C:\\tmp\\axis\\" + name))); byte[] buffer = new byte[1024 * 4]; int n = 0; while ((n = bis.read(buffer)) != -1) { bos.write(buffer, 0, n); } } finally { if (bis != null) { bis.close(); } if (bos != null) { bos.close(); } } } public static void main(String[] args) throws Exception{ Call call = createCall(); callReceive(call); callSend(call); } }
請注意上面 Client 程式中的第 31 ~ 35 行,在建出 Client 物件之後,要去註冊 DataHandler 類別與指定為該類別序列化與反序列化的類別。也許大家會覺得奇怪的是如果剛剛使用宣告 DataHandler 的方式來編寫 wsdd 檔的話,那時不是已經宣告了嗎?為什麼這裏還要宣告一次?這是因為 wsdd 檔是給 Server 看的 Client 並不知道,因此當 Client 在遇到非 Web Service 標準的 DataHandler 類別就會不知該怎麼去序列化與反序列化它了,所以在編寫 Client 端的呼叫程式時,仍然需要進行宣告/註冊 DataHandler 類別的步驟。
當這支 Client 程式執行成功後,將 url 改成呼叫用 base64Binary 取代宣告 DataHandler 的 wsdd 檔所部署的 Web Service (DataHandlerWS1_DP2),程式其他的部份不需要去動,執行後發現也能成功。這裏可能有人又有疑問了,那就是不是用 base64Binary 來取代 DataHandler 了嗎?為什麼還是要註冊 DataHandler 類別,而且傳送與接收 DataHandler 類別都沒問題呢?其實 base64Binary 只是告訴 Web Service 在傳送過程中使用這個型態,當接收端(Server 或 Client) 在收到資料時,只要能依你註刪的方式反序列回來,一樣是能轉成我們要的類別的。這部份只光用這樣描述似乎不大能理解,下一篇再來用實際的例子說明只要能反序列化出來,Server 與 Client 兩端傳遞的類別是可以不同的。
參考文件:
.用DataHandler来实现一个带附件的soap请求的web services
.第 9 章 使用 DataHandler 实现文件上传与下载
.用基于 AXIS 的 WebService 传输文件
.利用 AXIS 开发 WebService (五) --- 如何传递文件
.利用 axis 实现文件传递的问题 -- (問題解決)
留言列表