背景
早已经发布的项目,但是一直在下载ftp文件是有点问题,原来的项目只能下载小文件,应该介于20-100左右的文件,虽然你能应对基本的需求,但是总有一天会发现这是逃避问题,于是现在发现了,自己留的坑自己填吧!
项目是基于cxf webservice的,其中需要实现一些基本的查询以及下载功能。
相关知识点介绍(暂时假装能看得懂吧)
cxf
Apache CXF 是一个开源的 Services 框架。
CXF 帮助您利用 Frontend 编程 API 来构建和开发 Services ,像 JAX-WS 。
这些 Services 可以支持多种协议,
比如:SOAP、XML/HTTP、RESTful HTTP 或者 CORBA ,并且可以在多种
传输协议上运行,比如:HTTP、JMS 或者 JBI,CXF 大大简化了 Services 的创建,
同时它继承了 XFire 传统,一样可以天然地和 Spring 进行无缝集成。
MOTM
MTOM(Message Transmission Optimization Mechanism)消息优化传输机制。
它提出的模型适用于大量数据的交互情况。
针对Base64编码情况带来的开销提出的解决方案。
当数据量小的时候,SOAP依然使用XML进行消息的传递。
消息传输优化机制 (MTOM) 标准允许将消息中包含的大型数据元素外部化,
并将其作为无任何特殊编码的二进制数据随消息一起传送。
MTOM 消息会打包为多部分/相关 MIME 序列,放在SOAP 消息中一起传送。
但是在大量数据情况下,如果数据依然进行Base64编码,会带来33%的额外开销,
这样的情况对于大量数据交换的情况是无法容忍的。
MTOM 就是针对SOAP 消息传输的基础上提出的改进办法。
对于大量数据的传递,不会进行进行Base64编码,而是直接以附件的
二进制原始数据的形式封装在SOAP消息的 MIME 部分,进行传输。
SOAP 消息通过指向随其发送的 MIME 部分来引用二进制内容,
另外包括SOAP基本的XML 数据,这些还是Base64编码。
因为此模型与简单邮件协议SMTP 模型基本一致。
MTOM通过简化大量数据的编码过程,从而提高数据的处理效率。
因为SOAP消息等必要的信息,MTOM 也有一些必要的开销。
MTOM仅在二进制数据元素的大小超过大约 1 KB 时,才能体现出其优势。
MIME
表示多用途Internet邮件扩允协议。
MIME扩允了基本的面向文本的Internet邮件系统,以便可以在消息中包含二进制附件。MIME(Multipurpose Internet Mail Extentions),一般译作"多用途的网络邮件扩充协议"。
顾名思义,它可以传送多媒体文件。
MIME (Multipurpose Internet Mail Extensions,多目的Internet邮件扩展)是
创建用于电子邮件交换,网络文档,
及企业网和Internet上的其他应用程序中的文件格式的规范。
原理
MTOM需要XOP(XML-binary Optimized Packing)来传输二进制数据。MTOM 允许将消息中包含的大型数据元素外部化,并将其作为无任何特殊编码的二进制数据随消息一起传送。MTOM 消息会打包为多部分相关 MIME序列,放在SOAP 消息中一起传送。因此你可以看出MTOM 并不是将附件转为Base64 编码,这样可以大大的提高性能,因为二进制文件转Base64 编码会非常庞大。
所以在cxf中如何实现呢?
cxf实现MTOM
在我的项目里,我需要实现从ftp中下载文件,文件最终以byte[]形式返回。客户端调用时,将数组转换成自己需要的文件类型。
服务端
为了实现将消息中包含的大型数据元素外部话,并将其作为无任何特殊编码的二进制随消息一起传送。首先需要实现一个类,作为消息的携带者,其实就是对要上传或者下载的文件进行一个定义,具体如下:
import javax.activation.DataHandler;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlMimeType;
import javax.xml.bind.annotation.XmlRootElement;
//标注JAXB 在进行JAVA 对象与XML 之间进行转换时只关注字段,而不关注属性(getXXX()方法)
@XmlRootElement(name="resume")
@XmlAccessorType(XmlAccessType.FIELD)
public class Resume {
//需要上传下载的文件的其他属性
private String fileName;
private String fileType;
//这是一个二进制文件(application/octet-stream),
//也可以使用具体的MIME类型,譬如:image/jpg、image/gif 等,
//要考虑客户端是否有对应的类型,javax.activation.*中的MIME 的相关API 可以完成MIME 类型的自动识别。
@XmlMimeType("application/octet-stream")
//这里有人说必须是DataHandler类型,但是我这边测试用byte[]也是可以的
private byte[] fileData;
public byte[] getFileData() {
return fileData;
}
public void setFileData(byte[] fileData) {
this.fileData = fileData;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getFileType() {
return fileType;
}
public void setFileType(String fileType) {
this.fileType = fileType;
}
}
然后就是对这个类的操作,无非就是对其进行赋值,打个样:
//对resume进行赋值
byte[] fileData= (byte[]) downloadFtp(serviceID,url);
String fName = url.trim();
String temp[] = fName.split("/");
String fileName = temp[temp.length-1];
String file[]=fileName.split("\\.");
Resume resume=new Resume();
resume.setFileName(file[0]);
resume.setFileType(file[1]);
resume.setFileData(fileData);
return resume;
最后,最终要的一步:配置MTOM协议
<jaxws:properties>
<entry key="mtom_enabled" value="true"></entry>
</jaxws:properties>
打个样:
<jaxws:endpoint id="helloWorld" implementor="#HelloWorldImpl" address="/HelloWorld" >
<!-- 输入日志拦截器 -->
<jaxws:inInterceptors>
<ref bean="inMessageInterceptor"/>
</jaxws:inInterceptors>
<!-- 输出日志拦截器 -->
<jaxws:outInterceptors>
<ref bean="outMessageInterceptor"/>
</jaxws:outInterceptors>
<jaxws:properties>
<entry key="mtom_enabled" value="true"></entry>
</jaxws:properties>
</jaxws:endpoint>
这样服务端就配置完成了。
客户端
客户端也很简单,也还是服务端那三步:
- 添加信息携带者,和服务端的一样
- 对类进行操作,取值
- 配置MOTM,配置方法跟实现的客户端的写法有关系,这里就不写了
总结
总的来说,这个方法在项目里使用能够满足下载大文件的需求,至少1G以内不成问题。
但是在使用中也发现部分问题:
- 加上了MTOM,在客户端和服务端通信方面都要有所改动,负责会造成无法通信的问题.
- 配置了MTOM之后,貌似会影响速度,反正在我这里至少影响2-3秒,貌似是这个协议的问题,但是具体的还没有深究