5天學會jaxws-webservice程式設計第四天
前言:
從今天開始,我們將學習如何使用Webservice進行二進位制附件的傳輸,這一課題將分成兩個部分:
l 第一部分實現Client上傳一個附件到服務端,服務端接收Client傳過來的二進位制附件後儲存至本地。
第二部分實現Client上傳一個Java複雜型別,該複雜型別中有一個欄位叫myPhoto,為一個jpg/gif附件,服務端接受該上傳的複雜型別,並把其中的二進位制欄位中的圖片儲存至本地。
以下是我們要存取的圖片(搞笑一下啊)
目標:
1. 客戶端用Webservice上傳一個二進位制檔案,服務端收到客戶端request後把二進位制附件讀出,儲存至本地
一、編寫Server端
1.1 製作Service端
package ctsjavacoe.ws.fromjava; import javax.jws.WebMethod; import javax.jws.WebService; import javax.xml.ws.Holder; import java.io.*; @WebService public class MTOMSimple { @WebMethod public void echoData(Holder<byte[]> data) { OutputStream os = null; ByteArrayInputStream bin = null; try { bin = new ByteArrayInputStream(data.value); if (data.value != null) os = new FileOutputStream("D:/upload/jaxwsupload/echoData.jpg"); byte[] bytes = new byte[1024]; int c; while ((c = bin.read(bytes)) != -1) { os.write(bytes, 0, c); } os.close(); } catch (Exception e) { e.printStackTrace(); } finally { try { if (os != null) { os.close(); os = null; } } catch (Exception e) { } try { if (bin != null) { bin.close(); bin = null; } } catch (Exception e) { } } } } |
該Service有一個方法:
public void echoData(Holder<byte[]> data);
該方法中接收一個引數,該引數叫“Holder”,該方法在接受到客戶端的上傳請求後會從Holder型別的引數中匯出以byte[]為型別的二進位制流,然後寫入Server端的D:\upload\jaxwsupload目錄下,並且存成一個叫echoData.jpg檔案。
在java中方法呼叫“以值呼叫(call by value)”,即是通過原始值的複製傳遞的(是使用的變數的複製,而不是原始值)。
如果形參是物件引用,此時形參引用改變了物件的域,或者呼叫了改變物件狀態的方法,那麼對於持有該物件引用的其他程式碼而言,該物件改變了。
也就是說,IN引數是Java因有的引數,而OUT、INOUT引數不是Java固有的。
而JAX-WS2.0是支援OUT、INOUT引數的。
Holder就提供了一個措施,為不可變的物件引用提供一個可變的包裝,在java中支援引用傳遞。這樣就使Java可以與支援INOUT、OUT引數的程式語言寫的web service進行通訊。
1.3 編譯
此處的Webservice Server端生成的全部詳細過程請參見“第一天”教程中的描述。
1. 用wsgen來編譯生成相關的java檔案,wsdl檔案與xsd檔案;
2. 將編譯時輸出至wssrc目錄的檔案拷貝至src目錄;
3. 修改WebContent\WEB-INF目錄下的sun-jaxws.xml檔案,加入:
<endpoint name='MTOMSimple' implementation='ctsjavacoe.ws.fromjava.MTOMSimple' url-pattern='/MTOMSimpleService' /> |
4.修改WebContent\WEB-INF目錄下的web.xml加入:
<servlet> <servlet-name>MTOMSimple</servlet-name> <servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>MTOMSimple</servlet-name> <url-pattern>/MTOMSimpleService</url-pattern> </servlet-mapping> |
5. 將JaxWSProject的WebContent目錄下的檔案拷貝至tomcat的webapps\JaxWSSample
目錄下,並選擇全部覆蓋;
6.重啟Tomcat;
7.開啟一個IE瀏覽器,輸入:
http://localhost:9090/JaxWSSample/MTOMSimpleService?wsdl ,這時應該可以看到正確的輸出二、編寫Client端
2.1 編譯前的準備
此處的Webservice Client端生成的全部詳細過程請參見“第一天”教程中的描述。
1. 把Server端生成的wsdl與xsd拷貝至client工程的wsdl目錄下
2. 由於我們繼續使用polling方式來書寫非同步的客戶端呼叫,因此我們還需要開啟binding.xml檔案,更改一下:
<?xml version="1.0" encoding="UTF-8"?> <bindings xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" wsdlLocation="wsdl/MTOMSimpleService.wsdl" xmlns="http://java.sun.com/xml/ns/jaxws"> <bindings node="wsdl:definitions"> <enableAsyncMapping>true</enableAsyncMapping> </bindings> </bindings> |
3. 使用wsimport命令來生成client端呼叫時所需要的“控制代碼”
4. 把生成的控制代碼中的MTOMSimpleService.java這個檔案開啟,編輯它,將裡面兩處Url url=……的地方改成你的Server端實際的Webservice的wsdl地址,而預設它是指向一個本地的wsdl檔案的路徑
2.2 書寫Test客戶端呼叫Webservice的Server端
package ctsjavacoe.ws.fromjava; import com.sun.xml.ws.developer.JAXWSProperties; import javax.xml.ws.BindingProvider; import javax.xml.ws.Holder; import javax.xml.ws.soap.SOAPBinding; import javax.xml.ws.soap.MTOMFeature; import javax.xml.transform.stream.StreamSource; import javax.activation.DataHandler; import java.io.File; import java.io.InputStream; import java.io.FileInputStream; import java.awt.*; import java.util.Arrays; public class MTOMSimplePollingClient { public static void testEcho(MTOMSimple port) throws Exception { byte[] bytes = AttachmentHelper.getImageBytes(getImage("carrier.jpg"), "image/jpeg"); Holder<byte[]> image = new Holder<byte[]>(bytes); port.echoData(image); if (image.value != null) System.out.println("SOAP 1.1 testEcho() PASSED!"); else System.out.println("SOAP 1.1 testEcho() FAILED!"); } private static String getDataDir () { return "d:/"; } private static Image getImage(String imageName) throws Exception { String location = getDataDir() + imageName; System.out.println("image location=====>"+location); return javax.imageio.ImageIO.read(new File(location)); } public static void main(String[] args) { try { MTOMSimple port = new MTOMSimpleService() .getMTOMSimplePort(new MTOMFeature()); if (port == null) { System.out.println("TEST FAILURE: Couldnt get port!"); System.exit(-1); } // test echo testEcho(port); } catch (Exception ex) { System.out.println("SOAP 1.1 MtomApp FAILED!"); ex.printStackTrace(); } } } |
核心程式碼片段:
byte[] bytes = AttachmentHelper.getImageBytes(getImage("carrier.jpg"), "image/jpeg"); Holder<byte[]> image = new Holder<byte[]>(bytes); port.echoData(image); if (image.value != null) System.out.println("SOAP 1.1 testEcho() PASSED!"); else System.out.println("SOAP 1.1 testEcho() FAILED!"); |
請注意這邊的port.echoData…if…else…的用法。
在《1.2 javax.xml.ws.Holder的解釋》中提到過,“Holder就提供了一個措施,為不可變的物件引用提供一個可變的包裝,在java中支援引用傳遞”。
因此,當客戶端呼叫完Service的echoData(Holder)後, 再去判斷這個image是否為null,此時,如果Service端呼叫成功,它會把這個image回傳給客戶端(引用傳遞),如果呼叫失敗,它會返回一個null值給客戶端。
因此我們判斷這個image是否為null即可知道Service端是否成功執行且沒丟擲任何異常。
AttachmentHelper是一個用於處理二進位制圖片檔案的“工具類”,大家也可以根據這個類的設計思想寫自己的處理相關二進位制檔案的“工具類”,該類的程式碼如下:
package ctsjavacoe.ws.fromjava; import com.sun.xml.ws.util.ASCIIUtility; import javax.xml.transform.stream.StreamSource; import javax.imageio.ImageWriter; import javax.imageio.ImageIO; import javax.imageio.stream.ImageOutputStream; import java.awt.*; import java.awt.image.BufferedImage; import java.awt.image.ImageObserver; import java.awt.image.PixelGrabber; import java.io.IOException; import java.io.InputStream; import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; public class AttachmentHelper { public static boolean compareStreamSource (StreamSource src1, StreamSource src2) throws Exception { if (src1 == null || src2 == null) { System.out.println ("compareStreamSource - src1 or src2 null!"); return false; } InputStream is1 = src1.getInputStream (); InputStream is2 = src2.getInputStream (); if ((is1 == null) || (is2 == null)) { System.out.println ("InputStream of - src1 or src2 null!"); return false; } return Arrays.equals (ASCIIUtility.getBytes (is1), ASCIIUtility.getBytes (is2)); } public static byte[] getImageBytes(Image image, String type) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); BufferedImage bufImage = convertToBufferedImage(image); ImageWriter writer = null; Iterator i = ImageIO.getImageWritersByMIMEType(type); if (i.hasNext()) { writer = (ImageWriter)i.next(); } if (writer != null) { ImageOutputStream stream = null; stream = ImageIO.createImageOutputStream(baos); writer.setOutput(stream); writer.write(bufImage); stream.close(); return baos.toByteArray(); } return null; } private static BufferedImage convertToBufferedImage (Image image) throws IOException { if (image instanceof BufferedImage) { return (BufferedImage)image; } else { MediaTracker tracker = new MediaTracker (null/*not sure how this is used*/); tracker.addImage (image, 0); try { tracker.waitForAll (); } catch (InterruptedException e) { throw new IOException (e.getMessage ()); } BufferedImage bufImage = new BufferedImage ( image.getWidth (null), image.getHeight (null), BufferedImage.TYPE_INT_RGB); Graphics g = bufImage.createGraphics (); g.drawImage (image, 0, 0, null); return bufImage; } } public static boolean compareImages (Image image1, Image image2) throws IOException { if (image1 == null || image2 == null) return false; boolean matched = false; Rectangle rect = new Rectangle (0, 0, convertToBufferedImage (image1) .getWidth (), convertToBufferedImage (image1) .getHeight ()); Iterator iter1 = handlePixels (image1, rect); Iterator iter2 = handlePixels (image2, rect); while (iter1.hasNext () && iter2.hasNext ()) { Pixel pixel = (Pixel) iter1.next (); if (pixel.equals ((Pixel) iter2.next ())) { matched = true; } else { matched = false; } } if (matched) return true; return false; } private static Iterator handlePixels (Image img, Rectangle rect) { int x = rect.x; int y = rect.y; int w = rect.width; int h = rect.height; int[] pixels = new int[w * h]; PixelGrabber pg = new PixelGrabber (img, x, y, w, h, pixels, 0, w); try { pg.grabPixels (); } catch (InterruptedException e) { System.err.println ("interrupted waiting for pixels!"); return null; } if ((pg.getStatus () & ImageObserver.ABORT) != 0) { System.err.println ("image fetch aborted or errored"); return null; } ArrayList tmpList = new ArrayList (); for (int j = 0; j < h; j++) { for (int i = 0; i < w; i++) { tmpList.add (handleSinglePixel (x + i, y + j, pixels[j * w + i])); } } return tmpList.iterator (); } private static Pixel handleSinglePixel (int x, int y, int pixel) { int alpha = (pixel >> 24) & 0xff; int red = (pixel >> 16) & 0xff; int green = (pixel >> 8) & 0xff; int blue = (pixel) & 0xff; return new Pixel (alpha, red, green, blue); } private static class Pixel { private int a; private int r; private int g; private int b; Pixel (int a, int r, int g, int b) { this.a = a; this.r = r; this.g = g; this.b = b; } protected boolean equals (Pixel p) { if (p.a == a && p.r == r && p.g == g && p.b == b) return true; return false; } } } |
ü 在d:盤下建立目錄D:\upload\jaxwsupload
執行該客戶端,得到如下輸出:
在得到上述PASSED!的輸出資訊後,我們就可以檢視一下我們的D:\upload\jaxwsupload目錄:
可以看到,圖片已經通過服務端的Webservice生成至相應的目錄,並改名叫echoData.jpg檔案了。
三、解說MTOM
MTOM 是一種機制,用來以原始位元組形式傳輸包含 SOAP 訊息的較大二進位制附件,從而使所傳輸的訊息較小。
提到MTOM訊息優化傳輸機制,通常的實驗結果是使用MTOM傳輸資料會提高大約33%的效能。 訊息傳輸優化機制(MTOM) 標準允許將訊息中包含的大型資料元素外部化,並將其作為無任何特殊編碼的二進位制資料隨訊息一起傳送。MTOM訊息會打包為多部分/相關 MIME 序列,放在SOAP 訊息中一起傳送。
MTOM 全稱Message Transmission Optimization Mechanism,即訊息傳輸優化機制。它提出的模型適用於大量資料的互動情況。針對Base64編碼情況帶來的開銷提出的解決方案。當資料量小的時候,SOAP依然使用XML進行訊息的傳遞。
但是在大量資料情況下,如果資料依然進行Base64編碼,會帶來33%的額外開銷,這樣的情況對於大量資料交換的情況是無法容忍的。MTOM 就是針對SOAP 訊息傳輸的基礎上提出的改進辦法。對於大量資料的傳遞,不會進行進行Base64編碼,而是直接以附件的二進位制原始資料的形式封裝在SOAP訊息的MIME 部分,進行傳輸。SOAP 訊息通過指向隨其傳送的 MIME 部分來引用二進位制內容,另外包括SOAP基本的XML 資料,這些還是Base64編碼。因為此模型與簡單郵件協議SMTP 模型基本一致。
MTOM通過簡化大量資料的編碼過程,從而提高資料的處理效率。因為SOAP訊息等必要的資訊,MTOM 也有一些必要的開銷。MTOM僅在二進位制資料元素的大小超過大約 1 KB 時,才能體現出其優勢
通過今天的學習,我們知道了如何使用javax.xml.ws.Holder結合jaxws的MTOM特性來通過client端給server端上傳附件。
在第五天的教程中,將展示更強大的Java複雜型別混合二進位制檔案,實現真正意義上的“附件繫結上傳”功能。