转载自(https://my.oschina.net/u/2000201/blog/486744)
1 概述
Commons FileUpdate包很容易为你的Servlet和web应用程序添加健壮的、高性能的文件上传功能。
FileUpload解析遵循RFC 1876(在HTML中基于表单的文件上传)HTTP请求。即,如果一个HTTP请求使用POST方法提交,并
且使用“multipart/form-data”的内容类型,然后FileUpload解析请求,使结果易于调用者使用。
从1.3开始,FileUpload处理RFC 2047编码头值。
2 用户指南
2.1 使用FileUpload
FileUpload能使用大量不同的方式,依赖于你的应用程序的需求。在简单的情况下,你将调用简单的方法解析Servlet请求,
然后处理item列表作为它们应用到你的应用程序。在天平的另一端,你可能决定自定义FileUpload充分的控制单个item存储
的方式;例如,你可能决定将流的内容写入数据库。
这里,我们将描述FileUpload的基本原则,并阐述一些更简单的——并且更通用的——使用模式。
FileUpload依赖于Commons IO。
2.2 工作原理
一个文件上传请求包含一个根据RFC 1867(在HTML中基于表单的文件上传)编码的有序item列表。FileUpload能解析这么一个请求,并提供给你的应用程序单独的上传item列表。每个item实现FileItem接口,不管它底层实现。
本文描述Commons FileUpload类库的传统API。传统API是便利方式。然而,对于最终性能,你可能喜欢快速的流API。
每个文件item有你的应用程序可能感兴趣的许多属性。例如,每个item有一个名字和内容类型,并提供一个InputStream访问
它的数据。换句话说,你可能需要处理不同的item,依赖于是否item是常规表单——即,数据来自原始文本框或类似于HTML字段——或上传文件。FileItem接口提供方法做出这一决定,并以最适当的方式访问数据。
FileUpload使用FileItemFactory创建新文件item。这给FileUpload更大的灵活性。工厂最终控制每个item如何创建。工厂实
现当前过渡FileUpload存储item的数据在内存或磁盘,依赖于item的大小(例如,数据的字节)。然而,该行为能自定义适合你的应用程序。
2.3 Servlet和Portlet
从1.1开始,FileUpload支持Servlet和Portlet环境的文件上传请求。在两种环境中的使用几乎相同,因此,本文只讲述
Servlet环境。
如果你构建一个Portlet应用程序,以下两点不同你应该阅读API文档:
ServletFileUpload类->PortletFileUpload类
HttpServletRequest类->ActionRequest类
2.4 解析请求
你处理上传item之前,当然,你需要解析请求。确保,请求是一个简单的真实文件上传请求,但FileUpload使其变得简单,
通过提供一个静态方法做到这一点。
// 检查,我们有一个文件上传请求
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
现在,我们准备解析请求为item。
2.5 最简单的情况
以下是最简单的使用情景:
-
上传item应该保留在内容中,只要它相当小。
-
大型item应该写入磁盘上的临时文件。
-
非常大的上传请求应该不被允许。
-
内置默认的保存在内存中的一个item的最大大小,一个上传请求的最大大小,和可接受的临时文件位置。
在这种情况下,处理请求不应该更简单:
// 创建基于磁盘文件item的工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
// 配置一个仓库(确保一个安全的临时位置被使用)
ServletContext servletContext = this.getServletConfig().getServletContext();
File repository = (File) servletContext.getAttribute("javax.servlet.context.tempdir");
factory.setRepository(repository);
// 创建一个新的文件上传处理器
ServletFileUpload upload = new ServletFileUpload(factory);
// 解析请求
List<FileItem> items = upload.parseRequest(request);
这是我们的所有需要。
解析的结果是文件item List,每个FileItem接口的实现。
2.6 练习更多控制
如果你使用的情景是上面描述的最简单的情况,但是你需要一点更多的控制,你能易于定制上传处理器或文件item工厂的行为。
以下例子显示各种配置选项:
// 创建基于磁盘文件item的工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
// 设置工厂约束
factory.setSizeThreshold(yourMaxMemorySize);
factory.setRepository(yourTempDirectory);
// 创建一个新的文件上传处理器
ServletFileUpload upload = new ServletFileUpload(factory);
// 设置全部请求大小约束
upload.setSizeMax(yourMaxRequestSize);
// 解析请求
List<FileItem> items = upload.parseRequest(request);
当然,每个配置方法独立于其它方法,但如果你想要同时配置工厂,你能使用构造函数,像这样:
// 创建一个基于磁盘的文item工厂
DiskFileItemFactory factory = new DiskFileItemFactory(yourMaxMemorySize, yourTempDirectory);
你应该需要在请求解析上更高级的控制,例如,在其它地方存储item——例如,在数据库中。
2.7 处理上传item
一旦解析完成,你将有一个需要处理的文件item List。在大多数情况下,你将想要处理不同于普通表单字段的文件上传,
因此你可以像这样处理:
// 处理上传item
Iterator<FileItem> iter = items.iterator();
while (iter.hasNext()) {
FileItem item = iter.next();
if (item.isFormField()) {
processFormField(item);
} else {
processUploadedFile(item);
}
}
对于表单字段,你将只对item名称和它的String值感兴趣。正如你料想的,访问这些非常简单。
// 处理常规表单字段
if (item.isFormField()) {
String name = item.getFieldName();
String value = item.getString();
...
}
对于文件上传,有几种不同的东西,你可能会想知道你处理之前的内容。下面是一些你可能感兴趣的方法示例。
// 处理文件上传
if (!item.isFormField()) {
String fieldName = item.getFieldName();
String fileName = item.getName();
String contentType = item.getContentType();
boolean isInMemory = item.isInMemory();
long sizeInBytes = item.getSize();
...
}
使用上传文件,你通常不想通过内存要访问它们,除非它们很小,或者你没有其它选择。你宁愿将想要处理的内容作为一个流,或写入整个文件到它的最终位置。FileUpload提供简单的方法完成这两种。
// 处理文件上传
if (writeToFile) {
File uploadedFile = new File(...);
item.write(uploadedFile);
} else {
InputStream uploadedStream = item.getInputStream();
...
uploadedStream.close();
}
注意,在默认的FileUpload实现中,如果数据已经在临时文件中,write()将试图重命名文件到指定目标。
如果你需要访问内存中的上传数据,你需要简单调用get()方法获取数据作为byte数组。
// 处理内存中的文件上传
byte[] data = item.get();
...
2.8 资源清理
如果你使用DiskFileItem,你上传的文件处理之前被写入临时文件。这些临时文件被自动删除,如果他们不再使用,如果相应的java.io.File实例被垃圾回收。通过org.apache.commons.io.FileCleaner类默默开启一个清理线程。这个清理线程应该被停止,如果它不再需要。在Servlet环境中,通过使用一个特定Servlet上下文监听器(FileCleanerCleanup)来完成。为了这么做,在你的web.xml中添加:
<web-app>
...
<listener>
<listener-class>
org.apache.commons.fileupload.servlet.FileCleanerCleanup
</listener-class>
</listener>
...
</web-app>
2.9 创建DiskFileItemFactory
FileCleanerCleanup提供一个org.apache.commons.io.FileCleaningTracker实例。当创建org.apache.commons.fileupload.disk.DiskFileItemFactory时必须使用该实例。
public static DiskFileItemFactory newDiskFileItemFactory(ServletContext context, File repository) {
FileCleaningTracker fileCleaningTracker = FileCleanerCleanup.getFileCleaningTracker(context);
DiskFileItemFactory factory = new DiskFileItemFactory(DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD, repository);
factory.setFileCleaningTracker(fileCleaningTracker);
return factory;
}
2.10 禁用临时文件清理
为了禁用临时文件跟踪,你可以设置FileCleaningTracker为null。因此,创建文件将不再被跟踪。尤其,它们将不再被自动删除。
2.11 集成病毒扫描
病毒扫描和web容器运行在相同的系统中对于使用FileUpload的应用程序会导致一些意想不到的行为。本文描述一些你可能遇到的行为,并且提供一些如何处理它们的方式。
FileUpload的默认实现将导致上传的item超过某一阀值被写入磁盘。就这样一个文件关闭,系统上的任何病毒扫描程序会来检查它,并且可能隔离文件——即,把它移动到一个不能导致文件的地方。当然,这将给应用程序开发人员一个惊喜,因为上传文件item不再有效。话句话说,上传item在相同阀值下将保存在内容中,因此将不会被病毒扫描器发觉。这允许一个病毒可能以某种形式被保留(尽管从不写入磁盘,病毒扫描程序应该定位和检查它)。
一个常用的解决办法是将所有上传的文件放置系统的一个目录中,并配置病毒扫描器忽略该目录。这将确保应用程序不会丢掉文件,然而脱离病毒扫描程序的职责范围,对上传的文件进行病毒扫描可以由外部处理,移动干净或以清理了文件到“批准”位置,或在应用程序中集成病毒扫描程序。
2.12 查看进程
如果你期望真实的大文件上传,那么它将很好的报告给你的用户已经接收了多少。每个HTML页面允许实现一个进度条,通过返回一个multipart/replace响应或这样的东西。
查看上传进度可以通过提供一个进度监听器完成:
// 创建一个进度监听器
ProgressListener progressListener = new ProgressListener(){
public void update(long pBytesRead, long pContentLength, int pItems) {
System.out.println("We are currently reading item " + pItems);
if (pContentLength == -1) {
System.out.println("So far, " + pBytesRead + " bytes have been read.");
} else {
System.out.println("So far, " + pBytesRead + " of " + pContentLength
+ " bytes have been read.");
}
}
};
upload.setProgressListener(progressListener);
帮自己一个忙,实现你的第一个进度监听器,像上面这样,因为它显示了一个陷阱:进度监听器被频繁调用。依赖于Servlet引擎和其它环境工厂,它可能调用任何网络包!换句话说,你的进程监听器可能成为性能问题!一个典型的解决方法是减少进度监听器的活跃度。例如,你可以只在兆字节数量改变时发出消息:
// 创建进度监听器
ProgressListener progressListener = new ProgressListener(){
private long megaBytes = -1;
public void update(long pBytesRead, long pContentLength, int pItems) {
long mBytes = pBytesRead / 1000000;
if (megaBytes == mBytes) {
return;
}
megaBytes = mBytes;
System.out.println("We are currently reading item " + pItems);
if (pContentLength == -1) {
System.out.println("So far, " + pBytesRead + " bytes have been read.");
} else {
System.out.println("So far, " + pBytesRead + " of " + pContentLength
+ " bytes have been read.");
}
}
};
3 流API
3.1 为什么使用流?
假设,文件item实际被用户访问之前必须存储在某一地方。这种方式很方便,因为它允许易于访问item内容。话句话说,
它消耗内存和时间。
流API允许你牺牲一点点便利,优化性能和较低的内存配置。然而,流API更轻量级,因此易于理解。
3.2 工作原理
FileUpload类用于访问表单字段和字段顺序,它们已经通过客户端发送。然而,FileItemFactory完全被忽略。
3.3 解析请求
首先,不要忘记确保,请求实际是一个文件上传请求。这通常是通过使用相同的静态方法。
// 检查我们有一个文件上传请求
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
现在我们已经准备好解析请求:
// 创建新文件上传处理器
ServletFileUpload upload = new ServletFileUpload();
// 解析请求
FileItemIterator iter = upload.getItemIterator(request);
while (iter.hasNext()) {
FileItemStream item = iter.next();
String name = item.getFieldName();
InputStream stream = item.openStream();
if (item.isFormField()) {
System.out.println("Form field " + name + " with value "
+ Streams.asString(stream) + " detected.");
} else {
System.out.println("File field " + name + " with file name "
+ item.getName() + " detected.");
// 处理输入流
...
}
}