MTOM vs SwA
WebService传递文件有两种标准,SwA(SOAP with Attachment)和MTOM(Message Transmission and Optimization Mechanism)。SwA正在被性能更好的MTOM with XOP(XML Binary Optimized Packaging)所代替。如果不开启MTOM的话,则使用SwA。
MTOM相比较SwA而言,具有下面的优势:
SwA使用Base64binary传输,而MTOM使用ray binary传输。Base64binary比ray binary要多出33%的长度。
MTOM在Base64binary与raw binary之间的转化是透明的,转化发生在临近wire两端,并不影响程序员的对SOAP消息的更改。程序员不需要关心。
MTOM使用了现有的信息安全标准WS-Encryption和WS-Signatures,而SwA还需要自己照顾安全问题。
虽然MTOM具有传输优势,但对于小文件,过度的使用MTOM优化传输所带来的开销反而大于其传输所减少的时间。所以,对于MTOM可以设置一个值,当文件大小大于某值时,才开启MTOM。
借用下图,表示MTOM的工作方式。
MTOM开启方法
在服务器端,开启MTOM的方法有3
1, 在SEI实现类上使用@javax.xml.ws.soap.MTOM(threshold=1000). 此命令意思是对于1k以上的文件,开启MTOM。
2,在sun-jaxws.xml中声明<endpoint enable-mtom="true" .../>
3,在SEI实现类上使用@BindingType
@BindingType(value=javax.xml.ws.soap.SOAPBinding.SOAP11HTTP_MTOM_BINDING) 为soap1.1开启MTOM
@BindingType(value=javax.xml.ws.soap.SOAPBinding.SOAP12HTTP_MTOM_BINDING) 为soap1.2开启MTOM
在客户端,开启MTOM的方法有2
1,在创建Port的时候,使用MTOMFeature()作为参数。
2,像下面的代码一样,使用SOAPBinding
Hello port = new HelloService.getHelloPort();
//get the binding and enable mtom
SOAPBinding binding = (SOAPBinding) ((BindingProvider) port).getBinding();
boolean mtomEnabled = binding.isMTOMEnabled();
binding.setMTOMEnabled(true);
除了开启MTOM以外,还应该注意MIME数据与Java之间的转换。MTOM支持以下转换:
JAXB xmime:expectedContentType与java type之间的转换
在wsimport对wsdl转型的时候,若想将xml element转换成对应的java type,必须声明xmime:expectedContentType. 如:
<xs:complexType name="downloadImgResponse">
<xs:sequence>
<xs:element name="return" xmime:expectedContentTypes="image/jpeg" type="xs:base64Binary" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
在wsgen对java想wsdl转型的时候,若想将operation的参数和返回值转为相应的mine type,则需要使用annotation @XmlMimeType, 如:
public boolean uploadImg(String name, @XmlMimeType("image/jpeg") Image img);
@XmlMimeType("image/jpeg")
public Image downloadImg(String name);
MTOM实例
本实例将实现一个文件上传与下载服务,为了展示不同MIME类型不同的处理方式,在服务中将提供两类文件的上传与下载,image/jpeg和application/octet-stream.
package com.mycompany;
import java.awt.Image;
import javax.activation.DataHandler;
import javax.jws.WebService;
import javax.xml.bind.annotation.XmlMimeType;
@WebService
public interface AttachmentEngine {
// 上传图片
public boolean uploadImg(String name, @XmlMimeType("image/jpeg") Image img);
// 下载图片
@XmlMimeType("image/jpeg")
public Image downloadImg(String name);
// 上传二进制文件
public boolean uploadBinary(String name, @XmlMimeType("application/octet-stream") DataHandler handler);
// 下载二进制文件
@XmlMimeType("application/octet-stream")
public DataHandler downloadBinary(String name);
}
SEI实现类
package com.mycompany;
import java.awt.Image;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.imageio.ImageIO;
import javax.jws.WebService;
import javax.xml.bind.annotation.XmlMimeType;
import javax.xml.ws.WebServiceException;
import javax.xml.ws.soap.MTOM;
@MTOM
@WebService(endpointInterface="com.mycompany.AttachmentEngine")
public class AttachmentEngineImpl implements AttachmentEngine {
private final File saveDir = new File("d:/share/server");
// 将上传的图片以参数name命名,并存储到d:/share/server目录下。
@Override
public boolean uploadImg(String name, @XmlMimeType("image/jpeg") Image img) {
try {
ImageIO.write((RenderedImage) img, "jpg", new File(saveDir, name));
} catch (IOException e) {
throw new WebServiceException("Fail to upload.");
}
return true;
}
// 获取d:/share/server下名为name的图片
@Override
@XmlMimeType("image/jpeg")
public Image downloadImg(String name) {
File image = new File(saveDir, name);
try {
return ImageIO.read(image);
} catch (IOException e) {
throw new WebServiceException("Fail to download.",e);
}
}
// 将上传的文件以参数name命名,并存储到d:/share/server目录下。
@Override
public boolean uploadBinary(String name,
@XmlMimeType("application/octet-stream") DataHandler handler) {
try {
handler.writeTo(new FileOutputStream(new File(saveDir, name)));
return true;
} catch (FileNotFoundException e) {
throw new WebServiceException("Fail to upload.",e);
} catch (IOException e) {
throw new WebServiceException("Fail to upload.",e);
}
}
// 获取d:/share/server下名为name的文件
@Override
@XmlMimeType("application/octet-stream")
public DataHandler downloadBinary(String name) {
return new DataHandler(new FileDataSource(new File(saveDir, name)));
}
}
打包部署
创建sun-jaxws.xml
<?xml version="1.0" encoding="UTF-8"?>
<endpoints xmlns='http://java.sun.com/xml/ns/jax-ws/ri/runtime' version='2.0'>
<endpoint
name='attachment'
implementation='com.mycompany.AttachmentEngineImpl'
url-pattern='/service'/>
</endpoints>
然后打包成file.war, 最后部署到tomcat。
访问http://localhost:8080/file/service
生成客户端
首先使用wsimport生成客户端。最后写一个类来检验webservice是否工作。
public class App {
private static final File uploadDir = new File("d:/share/client/upload");
private static final File downloadDir = new File("d:/share/client/download");
public static void main( String[] args ) throws IOException {
// 生成一个开启MTOM的port
AttachmentEngine engine = new AttachmentEngineImplService().getAttachmentEngineImplPort(new MTOMFeature());
// 将upload目录下的a.JPG以另一个名字photo.jpg上传
engine.uploadImg("photo.jpg", ImageIO.read(new File(uploadDir, "a.JPG")));
// 从服务器端重新获取photo.jpg
Image img = engine.downloadImg("photo.jpg");
// 将服务器端获取的photo.jpg以download.jpg的名字存到download目录中。
ImageIO.write((RenderedImage) img, "jpg", new File(downloadDir, "download.jpg"));
// 上传b.pdf,改名为test.pdf
engine.uploadBinary("test.pdf", new DataHandler(new FileDataSource(new File(uploadDir, "b.pdf"))));
// 下载test.pdf
DataHandler handler = engine.downloadBinary("test.pdf");
// 将下载的test.pdf存入download目录
handler.writeTo(new FileOutputStream(new File(downloadDir, "download.pdf")));
}
}
在运行程序前,请先将a.JPG和b.pdf复制到upload目录下。
在运行完程序以后,可以查看服务器目录和下载目录,文件应该已经在那里了。