声明
英文原版版权© 2002-2008 The Apache Software Foundation
本文非官方中文版,是本文翻译者自愿翻译,与The Apache Software Foundation无任何关系。
当前Commons FileUpload版本为1.2.2。
Copyright 2010 王丛琳 放弃本翻译版本除署名权外的一切权利。
翻译完成时间1月22日。
如您对翻译有异议,或发现翻译错误,敬请不吝赐教。Email:con.by.g@gmail.com
使用FileUpload
FileUplaod 可以根据你的应用程序的要求,以各种方式使用。最简单的方式是,你调用一个方法来解析servlet request,然后根据您的应用程序的要求,处理上传表单。当然,你也可以配置FileUpload以适应不同的要求。例如,你可能要求将表单项或上传文件存储到数据库。
本教程只有是FileUpload的基本语法和简单常用的使用配置和方法。其他的定制方式放在这儿。
FileUpload依赖 Commons IO, 请确认Commons IO的版本是否在兼容列表上。
原理
文件上传请求是根据RFC 1867 ,"Form-based File Upload in HTML" 编写的有序的列表(List)。 FileUpload 能解析这样的请求并提供给你的应用程序一个特制的上传项目列表。这些特制的项目,都实现了FileItem接口,屏蔽了底层实现。
本文使用的是commons Fileupload 库的传统API。传统的API更方便,但如果要求更高的性能,你可能需要更快的Streaming API。
应用程序可能需要上传的文件的一些属性。例如,文件名、文件类型、使用InputStream访问的文件数据。当上传文件表单含有像text box的普通的HTML标签时,你可以根据FileItem接口中提供的方法,区分处理文件和普通标签数据。
FileUpload使用FileItemFactory工厂创建新项目。这给了FileUpload很大的灵活性。这个工厂控制着每个项目的生成和根据当前加载的项目的大小,决定将项目的数据存储到内存还是硬盘。当然,你可以根据需要定制。
Servlets and Portlets
从版本1.1开始,FileUplaod同时支持servlet和portlet环境。由于在这两种环境下使用方法几乎相同,所以本文只提到servlet环境。
如果你使用portlet,那么你应该阅读下面两点不同:
l 请用PortletFileUplaod类替换ServletFileUpload类。
l 请用ActionRequest类替换HttpServletRequest类。
解析请求
上传项目之前,你需要解析请求,确保它是一个真正的(actually)文件上传请求。FileUpload提供了一个静态方法,使你可以轻易做到。
// Check that we have a file upload request
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
现在,我们已经准备好解析请求的构成。
最简单的示例
最简单的示例要求如下:
l 上传文件应尽可能小的放在内存中。
l 较大的文件上传进行时,使用硬盘上的临时文件存储。
l 拒绝很大的文件上传请求。
l 允许指定内存中的最大值、上传文件大小的最大值和临时文件的位置。
处理这个示例中处理请求不能像下面这样简单:
// Create a factory for disk-based file items
FileItemFactory factory = new DiskFileItemFactory();
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload(factory);
// Parse the request
List /* FileItem */ items = upload.parseRequest(request);
这些都是必须的。准备好了!
解析的最后将返回一个含有实现了FileItem 接口的项目的列表(List)。下面将讨论处理这些项目。
使用更多功能
如果你的要求接近上面的示例,但要求还要多一点,你可以很容易的定制上传处理的行为和文件项目工厂(file item factory)。下面的例子展现了几个配置选项。
// Create a factory for disk-based file items
DiskFileItemFactory factory = new DiskFileItemFactory();
// Set factory constraints
factory.setSizeThreshold(yourMaxMemorySize);
factory.setRepository(yourTempDirectory);
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload(factory);
// Set overall request size constraint
upload.setSizeMax(yourMaxRequestSize);
// Parse the request
List /* FileItem */ items = upload.parseRequest(request);
当然,每个配置方法都是独立的,如果你想一次配置所有完成,你可向下面这样,使用另一个构造器:
// Create a factory for disk-based file items
DiskFileItemFactory factory = new DiskFileItemFactory(
yourMaxMemorySize, yourTempDirectory);
可能你需要更详细的控制,例如,将上传项目存储到数据库等等,那么,你需要去浏览自定义配置FileUplaod。
解析上传项
一旦解析完成,你将得到一个需要你处理的列表(List)。大部分情况下,你可能需要以不同的方式,处理上传的文件和普通的表单域,那么你可以像下面这样:
// Process the uploaded items
Iterator iter = items.iterator();
while (iter.hasNext()) {
FileItem item = (FileItem) iter.next();
if (item.isFormField()) {
processFormField(item);
} else {
processUploadedFile(item);
}
}
对于普通的表单域,你更可能只需要标签名称和它的字符串(String)值。正如你所期望的,访问他们很简单。
// Process a regular form field
if (item.isFormField()) {
String name = item.getFieldName();
String value = item.getString();
...
}
对于上传文件,你可能需要了解如下内容。下面是一些你可能感兴趣的方法。
// Process a file upload
if (!item.isFormField()) {
String fieldName = item.getFieldName();
String fileName = item.getName();
String contentType = item.getContentType();
boolean isInMemory = item.isInMemory();
long sizeInBytes = item.getSize();
...
}
对于上传的文件,一般不会直接在内存中处理,除非他们很小,或者是你实在是没其他方法了。相对的,你可能将它作为数据流(stream),或者将整个文件写入最终的存储位置。FileUpload提供了完成它们的简单方法。
// Process a file upload
if (writeToFile) {
File uploadedFile = new File(...);
item.write(uploadedFile);
} else {
InputStream uploadedStream = item.getInputStream();
...
uploadedStream.close();
}
注意,在FileUpload的默认实现中,如果数据已经存储在临时文件中,write()将试图重命名文件后复制到指定目标。若是因为某些原因(如,数据在内存中有引用)导致重命名失败,那么将只复制数据。
如果需要在内存中访问数据,那么只需简单的调用get()方法就可以得到一个保存着数据的字节数组。
// Process a file upload in memory
byte[] data = item.get();
...
资源清理
本章只适用于使用DiskFileItem。换一种说法就是,本章仅适用于使用临时文件的情况。
若是临时文件长时间未使用,将被自动删除,更确切的说, if the corresponding instance of java.io.File is garbage collected.(译者注:这句不知怎么翻译,总感觉是作者输入错误多了个if,无法理解。)。做这项工作的是org.apache.commons.io.FileCleaner类,它会启动一个收割线程。
如使长时间未使用,这个收割线程将被停止。在servlet环境中,它被当作一个名为FileCleanerCleanup的特殊的servlet上下文监听器。(译者注:另一种翻译,难以取舍。在servlet环境中,它被当作一个特殊的servlet上下文监听器,并称其为FileCleanerCleanup。)。
<web-app>
...
<listener>
<listener-class>
org.apache.commons.fileupload.servlet.FileCleanerCleanup
</listener-class>
</listener>
...
</web-app>
创建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);
return new DiskFileItemFactory(fileCleaningTracker,
DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD,
repository);
}
禁止清理临时文件
设置 FileCleaningTracker为null,就可以禁用跟踪(译者注:相对跟踪,本人感觉记录更合适)临时文件。以后,创建的文件将不再被跟踪。并且,它们将不再被自动删除。
关于杀毒软件
在杀毒软件和Web容器共存于同一个系统是,可能导致FileUpload产生意想不到的问题。本章描述了一些可能出现的问题和解决方法。
FileUpload的默认的实现将造成一定大小的上传文件被写入到磁盘。只要这样的文件被关闭,病毒扫描程序就被激活,然后扫描,有可能被隔离。就是说,把它移到到一个特别指定的地方,防止它产生安全问题。了,这可能给应用程序开发者一个大惊喜,因为他们会发现上传的文件不能被访问。另一方面,文件在内存中时,就不会被病毒扫描程序发现。这使得病毒有可能性某种形式保留下来(虽然都写入磁盘,病毒扫描程序会找到并检查)。
一个常用的解决办法是将所有上传的文件将被放置一个特定目录中,并配置病毒扫描程序忽略该目录。但是这样就把杀毒的责任交给应用开发者了。可以由一个外部进程扫描上传的文件,这可能会移动干净或清除恶意软件后的文件到“特定(approved)”的位置,或应用程序集成在一个病毒扫描器。外部的配置或集成病毒扫描到应用程序的细节超出了本文的范围。
观察进度
当用户上传大文件时,回馈给用户上传进度是个不错的想法。比如在HTML网页上放置一个进度条,或类似的东西。
观察上传进度可通过进度监听器:
//Create a progress listener
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引擎和运行环境的设置,它可能被任何网络数据包调用!换句话说,就是上面的进度监听器可能导致性能问题。一个典型的解决办法是,减少进度监听器的活动。例如,当兆字节数更改时,才发送一个信息。示例如下:
//Create a progress listener
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.");
}
}
};
最后
希望这个页面已经提供了如何使你的应用程序文件上传。欲了解更多本文相关的方法的详细介绍,以及其他可用的方法,你应该参考的JavaDocs 。
这里描述的使用方式应能满足绝大多数文件上传的需求。但是,如果你有更复杂的要求,FileUpload应该仍然能通过灵活的定制能力帮助你。