使用FileUpload
根据应用程序的需求,FileUpload可以以多种不同的方式使用。在最简单的情况下,您将调用单个方法来解析servlet请求,然后处理应用于应用程序的项目列表。另一方面,您可能会决定自定义FileUpload以全面控制单个项目的存储方式; 例如,您可能决定将内容流式传输到数据库中。
在这里,我们将描述FileUpload的基本原理,并说明一些更简单和最常见的使用模式。FileUpload的自定义在其他地方描述。
FileUpload依赖于Commons IO,因此在继续之前,请确保您的类路径中的依赖性页面上提供了该版本。
怎么运行的
文件上传请求包含按照RFC 1867 “HTML中的基于表单的文件上传” 进行编码 的项目的有序列表。FileUpload可以解析这样的请求,并为您的应用程序提供单独上传项目的列表。无论其基础实施如何,每个这样的项目都实现了FileItem接口。
该页面描述了commons fileupload库的传统API。传统的API是一种方便的方法。但是,为了获得最佳性能,您可能更喜欢更快的 Streaming API。
每个文件项目都有一些可能对您的应用程序感兴趣的属性。例如,每个项目都有一个名称和一个内容类型,并且可以提供一个InputStream来访问其数据。另一方面,您可能需要以不同方式处理项目,具体取决于项目是否为常规表单字段(即数据来自普通文本框或类似的HTML字段)或上传的文件。所述 的FileItem接口提供的方法,使这样的确定,并访问在最恰当的方式的数据。
FileUpload使用FileItemFactory创建新的文件项目。这是FileUpload大部分灵活性的原因。工厂对每件产品的创建方式都有最终控制权。目前随FileUpload提供的工厂实现将项目的数据存储在内存或磁盘上,具体取决于项目的大小(即数据字节)。但是,可以自定义此行为以适合您的应用程序。
Servlet和Portlet
从版本1.1开始,FileUpload支持servlet和portlet环境中的文件上载请求。这两种环境中的用法几乎完全相同,因此本文的其余部分仅涉及servlet环境。
如果您正在构建Portlet应用程序,那么在阅读本文档时应该对以下两点作出区分:
- 在您看到对ServletFileUpload类的引用的地方,替换PortletFileUpload类。
- 在看到对HttpServletRequest类的引用的地方,替换ActionRequest类。
解析请求
在您可以使用上传的项目之前,当然,您需要解析请求本身。确保请求实际上是一个文件上传请求很简单,但FileUpload通过提供静态方法来简化它本身。
//检查是否有文件上传请求
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
现在我们准备将请求解析为其组成部分。
最简单的情况
最简单的使用场景如下:
- 上传的项目应该保留在内存中,只要它们相当小。
- 较大的项目应写入磁盘上的临时文件。
- 非常大的上传请求不应该被允许。
- 要保留在内存中的项目的最大大小,上载请求的最大允许大小以及临时文件的位置的内置默认值是可以接受的。
在这种情况下处理请求不会简单得多:
//为基于磁盘的文件项目创建一个工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
//配置一个存储库(以确保使用安全的临时位置)
ServletContext servletContext = this.getServletConfig()。getServletContext();
File repository =(File)servletContext.getAttribute(“javax.servlet.context.tempdir”);
factory.setRepository(储存库);
//创建一个新的文件上传处理程序
ServletFileUpload upload = new ServletFileUpload(factory);
//解析请求
List items = upload.parseRequest(request);
这就是所需要的。真!
解析的结果是一个文件项列表,每个文件项实现FileItem接口。处理这些项目在下面讨论。
锻炼更多的控制
如果您的使用场景接近最简单的情况(如上所述),但您需要更多的控制权限,则可以轻松定制上传处理程序或文件项目工厂的行为。以下示例显示了几个配置选项:
//为基于磁盘的文件项目创建一个工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
//设置工厂约束
factory.setSizeThreshold(yourMaxMemorySize);
factory.setRepository(yourTempDirectory);
//创建一个新的文件上传处理程序
ServletFileUpload upload = new ServletFileUpload(factory);
//设置总体请求大小约束
upload.setSizeMax(yourMaxRequestSize);
//解析请求
List items = upload.parseRequest(request);
当然,每个配置方法都独立于其他配置方法,但是如果要一次性配置工厂,则可以使用其他构造方法来完成此配置,如下所示:
//为基于磁盘的文件项创建一个工厂
DiskFileItemFactory factory = new DiskFileItemFactory(yourMaxMemorySize,yourTempDirectory);
如果您需要进一步控制对请求的分析,例如在其他地方存储项目(例如,在数据库中),则需要查看自定义 FileUpload。
处理上传的项目
解析完成后,您将获得需要处理的文件项列表。在大多数情况下,您需要处理与常规表单字段不同的文件上传,因此您可以像这样处理列表:
//处理上传的项目
Iterator iter = items.iterator();
while(iter.hasNext()){ FileItem item = iter.next();
if(item.isFormField()){ processFormField(item); } else { processUploadedFile(item); }
}
对于一个常规的表单字段,您很可能只对该项目的名称和其字符串值感兴趣。正如你所期望的那样,访问这些非常简单。
//处理一个常规的表单域
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 [] data = item.get();
...
资源清理
本节仅适用于使用 DiskFileItem的情况。换句话说,它适用于在处理之前将上传的文件写入临时文件。
如果不再使用这些临时文件(更准确地说,如果DiskFileItem的相应实例 是垃圾收集的,这可以通过org.apache.commons.io.FileCleanerTracker 类来完成,这会启动一个收割线程。
如果不再需要这个收割线程,应该停止。在servlet环境中,这是通过使用名为FileCleanerCleanup的特殊Servlet上下文侦听器 完成的。为此,请在web.xml中添加如下部分:
... 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); DiskFileItemFactory factory = new DiskFileItemFactory(DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD,repository); factory.setFileCleaningTracker(fileCleaningTracker); 退货工厂;
}
禁用清理临时文件
要禁用临时文件的跟踪,可以将FileCleaningTracker设置 为空。因此,创建的文件将不再被跟踪。特别是,它们不会再被自动删除。
与病毒扫描程序的交互
运行在与Web容器相同的系统上的病毒扫描程序可能会导致使用FileUpload的应用程序出现意外行为。本节介绍您可能遇到的一些行为,并提供一些如何处理这些行为的想法。
FileUpload的默认实现会导致超过一定大小阈值的上传项目被写入磁盘。一旦这样的文件关闭,系统中的任何病毒扫描程序都会唤醒并检查它,并有可能隔离该文件 - 即将其移至特定位置,以免造成问题。当然,这对于应用程序开发者来说是一个惊喜,因为上传的文件项目将不再可用于处理。另一方面,低于相同阈值的上传项目将被保存在内存中,因此病毒扫描程序不会看到。这就允许以某种形式保留病毒的可能性(尽管如果它曾被写入磁盘,病毒扫描程序将定位并检查它)。
一种常用的解决方案是在系统中放置一个目录,将所有上传的文件放入其中,并配置病毒扫描程序忽略该目录。这可确保文件不会从应用程序下被删除,但会将病毒扫描的责任交给应用程序开发人员。扫描病毒上传的文件可以通过外部进程执行,这可能会将干净或清理的文件移动到“已批准”的位置,或者通过在应用程序本身内部集成病毒扫描程序。配置外部进程或将病毒扫描整合到应用程序的详细信息不在本文档的范围内。
看着进步
如果你期望真正的大文件上传,那么向用户报告已经收到了多少是很好的。甚至HTML页面也允许通过返回多部分/替换响应或类似的东西来实现进度条。
观察上传进度可以通过提供一个进度监听器来完成:
//创建一个进度监听器
ProgressListener progressListener = new ProgressListener(){ public void update(long pBytesRead,long pContentLength,int pItems){ System.out.println(“我们正在阅读item + pItems); 如果(pContentLength == -1){ System.out.println(“到目前为止,”+ pBytesRead +“字节已被读取。”); } else { System.out.println(“到目前为止,已经读取了”+ pContentLength +“字节的”+ pBytesRead +“。} }
};
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; 如果(megaBytes == mBytes){ return; } megaBytes = mBytes; System.out.println(“我们正在阅读项目”+ pItems); 如果(pContentLength == -1){ System.out.println(“到目前为止,”+ pBytesRead +“字节已被读取。”); } else { System.out.println(“到目前为止,”+ pContentLength“+ pBytesRead +”of“+ pContentLength+“字节已被读取。”); } }
};
下一步是什么
希望本页为您提供了一个关于如何在自己的应用程序中使用FileUpload的好主意。有关此处介绍的方法以及其他可用方法的更多详细信息,请参阅JavaDocs。
这里描述的用法应该满足大部分文件上传需求。但是,如果您有更复杂的需求,FileUpload应该仍然可以帮助您,因为它具有灵活的 自定义功能。