java webservice 传输文件
阅读本文前您需要以下的知识和工具:
本文的参考资料见 参考资料
本文的全部代码在这里 下载
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数据,通常有以下两种方法:
对于CLOB数据,不需要使用BASE64编码,可以直接作为SOAP Body的一部分或者作为MIME附件传输。
本文将使用以上两种方式,以图像传输为例子讨论在SOAP消息中传输BLOB、CLOB数据的方法。首先我们看怎么使用BASE64编码来传输图形。
回页首
使用BASE64编码传输BLOB数据
使用BASE64编码来传输BLOB数据的基本过程是:
下面我们结合图形传输来看具体的编程实现。在这里我们使用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格式有:
下面是一个带有普通文档文本的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消息中可能有多个附件,那么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,您可以从http://java.sun.com/webservices下载。然后把代码(axis目录)拷贝到%JWSDP_HOME%/webapps/目录下,启动Tomcat即可。主程序在axis/WEB-INF/classes/目录下,分别是GetImageByString.java和GetImageByAttachmentClient.java。