java webservice 傳輸文件 – JAVA編程語言程序開發技術文章

閱讀本文前您需要以下的知識和工具:

JavaTM Web Services Developer Pack 1.1,並且會使用初步使用
Apache axis1.1
初步瞭解JAX-RPC編程方法
SAAJ、JAXM編程的基本技能
有圖像處理的一般知識
本文的參考資料見 參考資料
本文的全部代碼在這裡 下載
BOLB、CLOB數據傳輸方法
在SOAP消息中,復雜的數據類型包括記錄、對象和結構等,還包括圖像、聲音等多媒體數據。在記錄、結構的數據,可以利用XML本身的機制進行表示,比如要表示一些查詢的圖書的信息,可以使用以下的方法進行表示:
例程1 在SOAP消息中表示復雜的數據
<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">     <soap-env:Header/>     <soap-env:Body>         <books:GetAllBooks xmlns:books="http://hellking.webservice.com">            <books:book id="isbn-34-234-xxxx-34">     <books:name>J2EE Web 服務開發 </books:name>     <books:publisher> 電子工業出版社 </books:publisher>     <books:price> xxxxx </books:price>     <books:category>計算機</books:category>     <books:description> xxxxxxxxx </books:description>     <books:author> xxxxxx </books:author>     <books:author> xxxxx </books:author>           </books:book>          <books:bookid="242334">          … </books:book>        </books:GetAllBooks>     </soap-env:Body> </soap-env:Envelope> 另一個例子: <myFavoriteNumbers   SOAP-ENC:arrayType="xsd:int[2]">    <number>3</number>     <number>4</number>  </myFavoriteNumbers>

除瞭使用上述的方式傳輸外,還可以使用序列化對象的方式。我們知道,Java中的序列化對象可以在網絡上傳輸、保存。具體的過程是把這些數據保存在可序列化的Java對象中,然後把此對象序列化傳輸到對方,對方對此序列化對象進行"解凍",然後獲得要傳輸的數據。相對於直接用XML表示數據,這種方式比較消耗系統資源。
SOAP消息基於XML技術,XML在表示文本方面有很大的便利性,但是如果要在XML中表示圖像、聲音等多媒體數據(這裡指把圖像、聲音等數據包含在同一個XML文件中,而不是使用外部實體),那麼就不是那麼簡單瞭。理論上,你也可以把要傳輸的BLOB、CLOB數據保存在序列化的Java對象中,然後以序列化的Java對象為載體進行傳輸。但是這些一種非常的低效的方法!
要在SOAP中傳輸BLOB數據,通常有以下兩種方法:
使用BASE64編碼,把要傳輸的數據直接作為SOAP Body中的一部分
作為MIME附件,附加在SOAP消息上
對於CLOB數據,不需要使用BASE64編碼,可以直接作為SOAP Body的一部分或者作為MIME附件傳輸。
本文將使用以上兩種方式,以圖像傳輸為例子討論在SOAP消息中傳輸BLOB、CLOB數據的方法。首先我們看怎麼使用BASE64編碼來傳輸圖形。
回頁首
使用BASE64編碼傳輸BLOB數據
使用BASE64編碼來傳輸BLOB數據的基本過程是:
在服務端讀取目標BLOB數據保存在byte[]中;
使用BASE64Encoder(編碼器,如sun.misc.BASE64Encoder)把byte[]編碼成String;
當客戶端發出請求時,就返回這個String;
客戶端從SOAP消息中讀取這個String;
使用BASE64Decode(解碼器,如sun.misc.BASE64Decoder)把String解碼成byte[];
對byte[]進行處理(如果時圖像,就把它還原成圖像;如果是聲音,將把它還原成聲音)
下面我們結合圖形傳輸來看具體的編程實現。在這裡我們使用JAX-RPC的方式,您需要有Apache axis引擎。
首先是確定服務端的接口,這裡定義瞭一個方法:getImage(String imageName)。如例程2所示。
例程2 JAX-RPC服務端接口
package com.hellking.webservice; public interface ImageServiceInterface extends java.rmi.Remote {     public java.lang.String getImage(java.lang.String in0) throws java.rmi.RemoteException; }

註意getImage方法返回的數據類型,它是String,也就是通過BASE64編碼後的String。
使用這個接口生成WSDL文件,可以使用下面的方法:
SET AXIS_HOME=<axis安裝目錄> set CLASSPATH=%CLASSPATH%; %AXIS_HOME%/axis-1_1/lib/axis.jar;  %AXIS_HOME%/axis-1_1/lib/jaxrpc.jar;  %AXIS_HOME%/axis-1_1/lib/saaj.jar; %AXIS_HOME%/axis-1_1/lib/commons-logging.jar; %AXIS_HOME%/axis-1_1/lib/commons-discovery.jar; %AXIS_HOME%/axis-1_1/lib/wsdl4j.jar;. java org.apache.axis.wsdl.Java2WSDL -o wp.wsdl http://localhost:8080/axis/services/ImageService"  -n "urn:ImageService" -p"com.hellking.webservice" "urn:ImageService" com.hellking.webservice.ImageServiceInterface

以上方法將生成一個名為wp.wsdl的WSDL文件,生成瞭WSDL文件後,就可以使用WSDL2Java生成JAX-RPC的框架,如下所示:
java org.apache.axis.wsdl.WSDL2Java -o . -d Session -s -S true  -Nurn:ImageService com.hellking.webservice wp.wsdl

這個框架中,有一個名為ImageServiceSoapBindingImpl的類,我們要在裡面增加實現方法。
修改後的ImageServiceSoapBindingImpl如例程3所示。
例程3 JAX-RPC服務端實現類
package com.hellking.webservice; import java.io.*; public class ImageServiceSoapBindingImpl implements com.hellking.webservice.ImageServiceInterface{     public java.lang.String getImage(java.lang.String in0) throws java.rmi.RemoteException {        String ret=new String();   try   {    byte[] bytes=new byte[1024000];//小於1M     InputStream in=ImageServiceSoapBindingImpl.class.getResourceAsStream(in0);    in.read(bytes);    ret=new sun.misc.BASE64Encoder().encode(bytes); //具體的編碼方法    in.close();   }   catch(FileNotFoundException e)   {    e.printStackTrace();   }   catch(java.io.IOException ex)   {    ex.printStackTrace();   }   return ret;     }  }

在上面的程序中,byte[] bytes=new byte[1024000]表示要傳輸的圖像內容小於1M,你可以根據具體情況設置,ImageServiceSoapBindingImpl.class.getResourceAsStream(in0)是獲得圖像文件的輸入流,in0是文件名,如test.jpg。這個程序中最關鍵的部分是:
new sun.misc.BASE64Encoder().encode(bytes);

它把byte[]編碼成目標String。
下面的工作就是編譯這些代碼,然後部署。您可以使用下面的方式進行部署:
java org.apache.axis.client.AdminClient deploy.wsdd

使用這中方式部署時,需要保證Apache axis引擎處於運行狀態。如果這個方式部署不成功,也可以直接把編譯好的代碼拷貝到目標應用中,如:
%TOMCAT_HOME%/webapps/axis/WEB-INF/classes/

接下來編輯%TOMCAT_HOME%/webapps/axis/WEB-INF/server-config.wsdd文件,在某個"</service>"後加入以下內容:
例程4 手工部署JAX-RPC應用
<service name="ImageService" provider="java:RPC">   <parameter name="allowedMethods" value="*"/>   <parameter name="wsdlPortType" value="ImageServiceInterface"/>   <parameter name="wsdlServicePort" value="ImageService"/>   <parameter name="className" value="com.hellking.webservice.ImageServiceSoapBindingSkeleton"/>   <parameter name="scope" value="Session"/>   <parameter name="wsdlTargetNamespace" value="urn:ImageService"/>   <parameter name="wsdlServiceElement" value="ImageServiceInterfaceService"/>  </service>

然後重新啟動Apache axis引擎。可以使用下面的方法在瀏覽器裡驗證ImageService是否已經成功部署:
http://localhost:8080/axis/services/ImageService?wsdl&method=getImage&name=test.jpg

這個地址您需要根據具體的情況更改。
如果成功部署,將在瀏覽器裡返回以下的內容:
例程5 調用ImageService返回的消息
<?xml version="1.0" encoding="UTF-8" ?>   <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>   <getImageResponse soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">   <getImageReturn xsi:type="xsd:string">   /9j/4AAQSkZJRgABAgEAYABgAAD/7Q0YUGhvdG9zaG9wIDMuMAA4QklNA+0KUmVzb2x1dGlvbgAA   AAAQAGAAAAABAAEAYAAAAAEAAThCSU0EDRhGWCBHbG9iYWwgTGlnaHRpbmcgQW5nbGUAAAAABAAA   AHg4QklNBBkSRlggR2xvYmFsIEFsdGl0dWRlAAAAAAQAAAAeOEJJTQPzC1ByaW50IEZsYWdzAAAA   CQAAAAAAAAAAAQA4QklNBAoOQ29weXJpZ2h0IEZsYWcAAAAAAQAAOEJJTScQFEphcGFuZXNlIFBy … AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==     </getImageReturn>   </getImageResponse>   </soapenv:Body> </soapenv:Envelope>

註意getImageReturn之間的數據是經過BASE64後的數據,xsi:type="xsd:string"表示它的數據類型是String。
下面來看怎麼在客戶端對SOAP消息進行處理。
例程6 處理SOAP 經過BASE64編碼後的消息
package com.hellking.webservice; import java.awt.*; import javax.swing.ImageIcon; import javax.swing.JFrame; public class GetImageByString extends ImageClient {  public Image getImageFromWebservice()  {   ImageIcon img=null ;   com.hellking.webservice.ImageServiceInterface service;    try {        service = new com.hellking.webservice.ImageServiceInterfaceServiceLocator().getImageService();     String result=service.getImage("test.jpg");    byte[] bytes = new sun.misc.BASE64Decoder().decodeBuffer(result);    img = new ImageIcon(bytes);   }   catch (Exception re) {   re.printStackTrace();   }   return img.getImage();  }  public static void main(String[] args)  {   new GetImageByString();  } }

解碼的方法是編碼的逆過程,同樣很簡單。
byte[] bytes = new sun.misc.BASE64Decoder().decodeBuffer(result)

把JAX-RPC調用結果解碼成byte[];img = new ImageIcon(bytes)使用解碼後的byte[]來構建一個ImageIcon。
最後看一下GUI程序怎麼使用Image結果。
例程7 在GUI客戶端使用BASE64解碼後的Image
package com.hellking.webservice; import java.awt.*; import javax.swing.ImageIcon; import javax.swing.JFrame; public abstract class ImageClient extends JFrame {  public ImageClient()  {   super("test image transport");    setSize(800, 600);   setVisible(true);  }  public abstract Image getImageFromWebservice();  public void paint(Graphics  g)  {   super.paint(g);    Image img = getImageFromWebservice();         g.drawImage(img,0,0,null);    }   }

ImageClient是客戶端GUI程序的框架,前面的GetImageByString繼承瞭它。GetImageByString的目的是獲得Image,ImageClient的目的是顯示Image。GetImageByString運行的結果如圖1所示。

 
註意:運行此程序需要保證服務器端有對應的圖像文件,比如test.jpg。
對於不是圖像的BLOB數據,您可以通過特定的方式處理,比如保存到文件中。
例程8 保存結果
PrintWriter out1=null;      try      {        out1=new PrintWriter(new BufferedWriter(new FileWriter("re.out")));      }      catch(Exception e)      {    e.printStackTrace();      }            try      {            int i=0;     while(true)     out1.print(bytes[i++]);       }   catch(Exception e)   {    e.printStackTrace();    out1.close();   }

需要指出的是,不同的BLOB數據,在保存時要對格式進行不同的處理,如果使用上面的方法保存圖片,將不能得到正確的結果,需要進行額外的處理。具體的處理方式,已經是本文的題外話瞭。本案例的詳細代碼您可以從這裡 下載。
下面我們討論使用附件的形式在SOAP中傳輸BLOB數據。
回頁首
使用附件(Attachment)傳輸BLOB數據
在SOAP消息中,允許我們像郵件一樣發送附件。附件由另一份文檔或者份圖像內容組成。一般而言,附件應該采用文本或者二進制數據格式來表示。在英特網上,GIF和JPEG數據格式是圖形傳輸的事實標準。SOAP消息允許帶有一個或者多個MIME格式的附件。常用的MIME格式有:
text/html:普通的HTML
text/xml:XML文檔
text/plain:普通的文本
image/jpeg:JPEG圖像
image/tiff:tiff圖像
下面是一個帶有普通文檔文本的SOAP消息的一部分。
例程9 帶有附件的SOAP消息
——=_Part_2_25899876.1056182982030 Content-Type: text/xml <soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/"> <soap-env:Header> </soap-env:Header> <soap-env:Body> <getImage> <test>getImagebyAttachment_Test</test> </getImage> </soap-env:Body> </soap-env:Envelope> ——=_Part_2_25899876.1056182982030 Content-Type: text/plain Content-Id: hello hello,this is the soap attachment content! ——=_Part_2_25899876.1056182982030–

上面的黑體字是具體的附件的內容。它的Content-Type是text/plain,id是hello。
SAAJ(SOAP with Attachments API for Java)為發送帶附件的SOAP消息提供瞭編程接口。在編程中,我們使用SOAPMessage對象來創建AttachmentPart(SOAP 附件)對象,如下:
AttachmentPart attachment = soapMessage.createAttachmentPart();

每個AttachmentPart對象有一個或者多個headers和它相關聯。Content-Type是Header中必須的元素,其它的元素如:Content-Id,Content-Location是Header中可選的元素。如果要創建一個簡單的文本附件,可以使用下面的方式。
例程10創建一個簡單的帶文本的SOAP附件
String stringContent = " hello,this is the soap attachment content!"; attachment.setContent(stringContent, "text/plain"); attachment.setContentId("test_content"); soapMessage.addAttachmentPart(attachment);

上面創建的附件的MIME類型是text/plain。如果要創建一個圖形的MIME附件,可以使用下面的方法。
例程11 創建一個圖形的MIME附件
AttachmentPart attachment = soapMessage.createAttachmentPart() byte[] bytes=new byte[1024000]; InputStream in=GetImageByAttachment.class.getResourceAsStream("test.jpg"); in.read(bytes); ByteArrayInputStream stream = new ByteArrayInputStream(bytes); attachment.setContent(stream, "image/jpeg"); attachment.setContentId("test_content"); soapMessage.addAttachmentPart(attachment);

也許你覺得這樣創建圖形附件比較麻煩,下面有一種相對容易的創建方法。
例程12另一個創建一個圖形的MIME附件的方法
URL url = new URL("http://localhost:8080/axis/test.jpg"); DataHandler dh = new DataHandler(url); AttachmentPart attachment2 = message.createAttachmentPart(dh); attachment2.setContentId("myImage"); soapMessage.addAttachmentPart(attachment2);

這裡使用瞭DataHandler對象,它是JavaBean Activation Framework (JAF)的一部分,使用它來創建SOAP附件相對直接,首先創建一個URL,這個URL表示瞭圖像文件存儲的位置,然後使用這個URL來創建一個DataHandler對象,接下來就使用這個DataHandler來創建SOAP附件瞭。
接下來我們來編寫一個具體的例子?飧隼?雍頹懊嫻睦?右謊??彩竊贇OAP消息中傳輸一個圖像文件,不同的是使用附件的方式。
這裡的附件端采用瞭JAXM Servlet,關於JAXM編程的請參考此系列文章的第一篇(用JAXM開發Web服務)。服務端的代碼如下:
例程13 SOAP消息發送服務端
package com.hellking.webservice; import org.apache.soap.util.mime.*; import java.net.*; import java.util.*; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import javax.xml.soap.*; import javax.activation.*; import javax.xml.messaging.*; import javax.xml.transform.stream.StreamSource; public class GetImageByAttachment  extends JAXMServlet  implements ReqRespListener {  public SOAPMessage onMessage    (SOAPMessage message)       {         try   {    message.writeTo(System.out);    //以下部分創建SOAP消息             SOAPConnectionFactory soapConnectionFactory =                  javax.xml.soap.SOAPConnectionFactory.newInstance();          SOAPConnection soapConnection =                  soapConnectionFactory.createConnection();            MessageFactory messageFactory =                  MessageFactory.newInstance();          SOAPMessage soapMessage =                  messageFactory.createMessage();          SOAPPart soapPart = soapMessage.getSOAPPart();         SOAPEnvelope requestEnvelope =              soapPart.getEnvelope();         SOAPBody body = requestEnvelope.getBody();         SOAPBodyElement operation = body.addBodyElement             (requestEnvelope.createName("image"));                         //創建附件              URL url = new URL("http://localhost:8080/axis/test.jpg");//圖像文件的儲存的位置    DataHandler dh = new DataHandler(url);    AttachmentPart attachment2 = message.createAttachmentPart(dh);    attachment2.setContentId("myImage");//使用JAF來創建SOAP mime附件       soapMessage.addAttachmentPart(attachment2);                                 soapMessage.writeTo(System.out);             soapMessage.saveChanges();             return soapMessage;         }         catch(Exception ex)         {          ex.printStackTrace();          return null;         }             } }

當然,除瞭使用DataHandler來創建SOAP消息外,還可以按照前面介紹的使用基本的IO的方式來創建。
下面來看客戶端的代碼。如例程14所示。
package com.hellking.webservice; import javax.xml.soap.*; import java.io.*; import java.awt.image.*; import java.awt.*; import com.sun.image.codec.jpeg.*;  import javax.swing.JFrame; public class GetImageByAttachmentClient extends ImageClient {       String endPointURLString =  "http://localhost:8080/axis/servlet/GetImageByAttachment";  public static void main(String[] args)throws Exception  {   new GetImageByAttachmentClient();  }    public Image getImageFromWebservice()  {            try         {            //創建SOAP消息          SOAPConnectionFactory soapConnectionFactory =                  javax.xml.soap.SOAPConnectionFactory.newInstance();          SOAPConnection soapConnection =                  soapConnectionFactory.createConnection();            MessageFactory messageFactory =                  MessageFactory.newInstance();          SOAPMessage soapMessage =                  messageFactory.createMessage();          SOAPPart soapPart = soapMessage.getSOAPPart();          SOAPEnvelope requestEnvelope =              soapPart.getEnvelope();          SOAPBody body = requestEnvelope.getBody();          SOAPBodyElement operation = body.addBodyElement                  (requestEnvelope.createName("getImage"));                   javax.xml.soap.SOAPElement element =                          operation.addChildElement(requestEnvelope.createName("test"));   operation.addChildElement("testgetImage").addTextNode("getImagebyAttachment_Test");    soapMessage.writeTo(System.out);                   //使用JAXM進行調用     javax.xml.soap.SOAPMessage returnedSOAPMessage =                  soapConnection.call(soapMessage, endPointURLString);                 System.out.println("========");         returnedSOAPMessage.writeTo(System.out);//打印返回的SOAP消息         System.out.println("========");         java.util.Iterator it = returnedSOAPMessage.getAttachments();          BufferedImage image=null ;    //獲得附件,逐個對附件進行處理 while (it.hasNext()) {          AttachmentPart    attachment = (AttachmentPart)it.next();      printAttachmentInfo(attachment);      image= decodeImage(attachment.getDataHandler().getInputStream());      }    return image;   }   catch(Exception e)   {    e.printStackTrace();    return null;   }     }  //把附件解碼成Image  public BufferedImage decodeImage(java.io.InputStream in)throws java.io.IOException  {   BufferedImage image=null ;   image = new BufferedImage(600,800, BufferedImage.TYPE_INT_RGB);           com.sun.image.codec.jpeg.JPEGImageDecoder dencoder = JPEGCodec.createJPEGDecoder(in);    image=dencoder.decodeAsBufferedImage();    return image;  }  //打印附件的一些信息  private void printAttachmentInfo( AttachmentPart attachment)throws Exception  {   Object content = attachment.getContent();   System.out.println("ContentLocation="+attachment.getContentLocation());   System.out.println("Contentsize="+attachment.getSize());      System.out.println("ContentType="+attachment.getContentType());      String id = attachment.getContentId();        System.out.print("Attachment " + id + " contains: " +content);  }    }

(對於上面這段程序,請原諒我使用瞭一個特別龐大的getImageFromWebservice方法,並且使用瞭一個超級長的try{}語句,因為我實在沒有時間來把它寫得漂亮一點瞭^_^)
客戶端的執行過程是這樣的:
創建SOAP消息
發送SOAP消息
獲得返回結果
從返回的SOAP消息中獲得Attachment
對Attachment進行處理
由於一個SOAP消息中可能有多個附件,那麼returnedSOAPMessage.getAttachments()方法獲得的可能是一個Iterator,所以要遍歷這個Iterator對SOAP消息的附件進行處理。attachment.getDataHandler().getInputStream()獲得瞭附件的輸入流,DecodeImage是從這個輸入流進行獲得輸入,然後把它們解碼成Image。這裡的解碼方式和BASE64解碼方式稍有不同,BASE64解碼方式是把String類型的對象解碼成byte[],具體我們使用瞭sun.misc.BASE64Decoder類的decodeBuffer方法;這裡的解碼是把一個輸入流中的數據解碼成BufferedImage。
當然,這裡舉例的是圖像的處理,如果是別的格式數據,您同樣可以進行其它的處理。由於篇幅的限制,在這裡就不在贅述瞭。 你同樣可以運行GetImageByAttachmentClient來測試運行的效果。需要指出的是,使用這種方式來傳輸圖像時效率比前一種方法好,響應速度(啟動客戶端到在客戶端打印出圖像的時間)大概是使用BASE64編碼速度的一倍(我的機器環境是:WinXP,AMD Athlon XP 16000+,512M內存,Tomcat 4.03),您可以在您的機器上測試,如果您的測試結果比我的相差很多,您可以通過email告訴我,到時我們可以深入討論這個效率問題。
運行代碼說明:
運行前,需要安裝JWSDP1.1。然後把代碼(axis目錄)拷貝到%JWSDP_HOME%/webapps/目錄下,啟動Tomcat即可。主程序在axis/WEB-INF/classes/目錄下,分別是GetImageByString.java和GetImageByAttachmentClient.java。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *