apache commons fileupload 用户手册(2016-07-01更新)

此次说明更新对上传文件回收的监听器类做了变更,说明旧的监听器类可能已被淘汰并失效。

------------------------------------

使用FileUpload(Using FileUpload

FileUpload可以根据你应用程序(application)的需求(requirement)而使用到许多不同的地方。最简单地当它被应用到你的应用程序(application)时,你可以调用(call)一个朴素的方法来解析(parseservlet请求,然后处理文件条目列表。另一方面,你可能想自定义(customizeFileUpload来尽情控制文件被存储的地方;例如,你可能想输出文件内容到一个数据库。

在此,我们将描述FileUpload的基本原则, 以及说明一些简单的非常普遍的使用方式(usage pattern)。FileUpload的自定义(customization)被描述在其他

FileUpload依赖Commons IO因此在进行下一步前请确保你的classpath中有依赖页面中提到的版本。

工作原理(How it works

一个上传文件的请求包括一个按照RFC 1867进行编码的有序文件条目列表,这就是HTML中基于表单的文件上传。FileUpload能解析(parse)请求并为你的应用程序(application)提供一个用户上传文件条目的列表。每个这样的条目都是FileItem 接口的实现类对象,这就封装(regardless of)了它的底层实现。

本页描述了commons fileupload包的传统(traditionalAPI。传统(traditionalAPI是快速上手的使用途径。然而为了最佳(ultimate)性能,你可能更愿意使用更快的Streaming API

每个文件条目都有许多属性,它们可能是你的应用程序(application)所关注(interest)的内容。例如,每个条目都有名称和内容类型,并且可以提供一个InputStream来访问文件数据。另一方面,你可能需要根据条目是常规表单字段(这是一个基于普通文本框或类似HTML字段获取的数据)还是一个上传文件来以不同的方式处理它。FileItem接口提供了决定具体方式的方法(method),并以最适当的方式访问数据。 

FileUpload使用FileItemFactory来创建新文件条目。这是FileUpload获得最大扩展性的地方。工厂有创建哪一个文件的最终(ultimate)控制权。那些在FileUpload中预定义的工厂实现类根据条目大小(即数据字节大小)把条目存储(store)到内存(memory)或硬盘(disk)中。然而,你还可以自定义(customize)适合于你应用程序(application)的方式进行处理。

Servlet及Portlet(Servlets and Portlets

1.1开始,FileUploadservletportlet环境下都支持文件上传需求。在两个环境下使用1都是一样的,因此该文档稍后的部分只针对servlet环境进行描述。

如果你正构建一个portlet应用程序(application),当你读这个文档时以下两点区别是应该处理的:

那些你看到针对ServletFileUpload类的地方,需要替换为PortletFileUpload类。

那些你看到针对HttpServletRequest类的地方,需要替换为ActionRequest类。 

解析请求(Parsing the request

在你能处理上传的文件之前,想当然地你需要去解析(parse)请求(request)。要确保请求(request)确实是一个文件上传请求(request)是一件小事,FileUpload通过提供一个静态方法处理而使它更简单。

// 检查我们是否有一个文件上传请求(request
boolean isMultipart = ServletFileUpload.isMultipartContent(request);

现在我们准备好去解析(parse)请求(request)为组成请求(request)的条目了。

最简单的案例(The simplest case

以下是最简单的使用场景:

上传的文件条目应该以尽量小地被持有在内存(memory)中。

稍大的文件条目应该被写入到硬盘(disk)的临时文件中。

太大的文件上传请求应该被拒绝。

其内置(built-in)的可保持在内存(memory)中的文件大小最大值的默认值、允许上传的文件大小最大值以及临时文件保存的位置都是令人满意的。

在这个场景下,处理一个请求(request)应该没有更简单的了。

// 创建一个基于硬盘的文件条目工厂
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);

这就是所需要的全部了。是的!

解析(parse)的结果是一个文件条目列表,每一个都是FileItem接口的实现类。我们将在下面讨论如何处理这些文件条目。

使用更多控制权(Exercising more control

如果你的使用场景跟上面描述的最简案例很接近,但需要些许调整(control),你可以轻易地自定义(customize)上传处理程序的行为或文件条目工厂。以下例子展示了一些配置项:

// 创建一个基于硬盘的文件条目工厂
DiskFileItemFactory factory = new DiskFileItemFactory();

// 设置工厂约束
factory.setSizeThreshold(yourMaxMemorySize);
factory.setRepository(yourTempDirectory);

// 创建一个文件上传处理程序对象
ServletFileUpload upload = new ServletFileUpload(factory);

// 设置上传文件最大值约束
upload.setSizeMax(yourMaxRequestSize);

// 解析请求
List<FileItem> items = upload.parseRequest(request);

当然,每一个配置方法都不影响其他的配置项,但如果你想一次性配置工厂的所有配置项,你可以用另一个构造方法(constructor)来完成,就像这样:

// 创建一个基于硬盘的文件条目工厂
DiskFileItemFactory factory = new DiskFileItemFactory(yourMaxMemorySize, yourTempDirectory);

如果你需要更进一步的在解析(parse)请求(request)上的控制权,例如配置文件条目保存在例如数据库或其他地方,你将需要查看FileUpload自定义

处理上传文件条目(Processing the uploaded items

一旦解析(parse)完成,你将得到一个List 类型的需要你处理的文件条目集合。在大部分情况下,你会想根据不同规则的表单字段区别处理文件上传,因此你可能像这样处理这个列表:

// 处理文件上传条目
Iterator<FileItem> iter = items.iterator();
while (iter.hasNext()) {
    FileItem item = iter.next();

    if (item.isFormField()) {
        processFormField(item);
    } else {
        processUploadedFile(item);
    }
}

对于一个规则的表单字段,你将横可能只关注文件条目的名称,以及它的String 类型值正如你可能预料的,处理这些是非常简单的。

// 处理一个规则的表单字段
if (item.isFormField()) {
    String name = item.getFieldName();
    String value = item.getString();
    ...
}

对于一个文件上传来说,在你处理内容(content)之前你可能还有许多不同的信息想去了解。这里是一个一些你可能关注的方法的例子。

// 处理一个文件上传
if (!item.isFormField()) {
    String fieldName = item.getFieldName();
    String fileName = item.getName();
    String contentType = item.getContentType();
    boolean isInMemory = item.isInMemory();
    long sizeInBytes = item.getSize();
    ...
}

对于文件上传,你宁可将内容(content)作为一个输入输出流(stream)来处理,或写入整个文件到它的目标(ultimate)文件夹。一般也不会想通过内存(memory)处理它们,除非它们很小,或者你没有其他备选方案(alernative)。FileUpload提供了简单的方法来达成以上功能。

// 处理一个文件上传
if (writeToFile) {
    File uploadedFile = new File(...);
    item.write(uploadedFile);
} else {
    InputStream uploadedStream = item.getInputStream();
    ...
    uploadedStream.close();
}

注意,在FileUpload的默认实现中,如果数据已经在临时文件夹中存在,那么write()将尝试在文件夹(specified destination)对文件重命名。实际上,如果因为一些原因重命名失败或文件是存在内存(memory)中,FileUpload才会复制数据。

如果你需要在内存(memory)中访问上传数据,你只需要简单地调用(callget()方法来获取一个字节数组的数据。

// 处理一个在内存中的文件上传
byte[] data = item.get();
...

临时文件清理(Resource cleanup

这部分只适用于你使用DiskFileItem的情况。换句话说,它适用于你的上传文件在处理之前被写入到临时文件的情况。

如果临时文件不再使用(更确切地说如果对应的DiskFileItem对象被垃圾回收(garbage collected)),它们将被自动删除。org.apache.commons.io.FileCleanerTracker类启动一个回收(reaper)线程(thread)来默默地完成这项工作。

如果它不再被需要,这个回收(reaper)线程(thread)应该被关闭。在一个servlet环境下,这将通过使用一个特别的servlet内容监听器来完成,它被称为FileCleanerCleanup。需要该功能,只需像一下部分加入到你的web.xml

<web-app>
  ...
  <listener>
    <listener-class>
      org.apache.commons.fileupload.servlet.FileCleanerCleanup
    </listener-class>
  </listener>
  ...
</web-app>

创建一个DiskFileItemFactory(Creating a DiskFileItemFactory

FileCleanerCleanup 提供了一个org.apache.commons.io.FileCleaningTracker对象。当创建一个org.apache.commons.fileupload.disk.DiskFileItemFactory对象时org.apache.commons.io.FileCleaningTracker将被使用。它应该如下调用(call)一个方法来进行处理:

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;
}

禁用临时文件清理(Disabling cleanup of temporary files

想禁止监听(track)临时文件,你只需设置FileCleaningTrackernull。这样创建的文件将不再被监听(track)。具体来说,它们将不再被自动删除了。

与杀毒软件交互(Interaction with virus scanners

当应用程序(application)使用FileUpload且在同一个系统中运行杀毒软件(Virus scanners)时,会导致web容器出现一些预期以外的行为。这部分会描述一些你可能遇到的行为以及提供一些处理它们的办法。

FileUpload的默认实现可能导致将一个超过确定文件大小的阀值的上传文件条目写入到硬盘(disk)。一旦文件对象被关闭,任何系统中的杀毒软件(virus scanner)都将唤醒(wake up)并检查它进而可能对它进行隔离,即把它移动到一个特别的目录,在那里的文件不会导致安全风险。当然,由于上传文件条目将不再能被处理,这对于应用程序(application)开发者来说将是一个意料之外的情况。另一方面,上传文件条目小于某阀值将被持有在内存(memory)中,因此将不被杀毒软件(virus scanner)发现。这导致病毒有可能在一些表单中保留(尽管每当它被写入硬盘(disk)杀毒软件(virus scanner)都将检查它)。

一个通用的解决方案是在系统中设置另一个目录来放置所有上传文件条目,并且配置杀毒软件(virus scanner)忽略这个文件夹。这能确保文件不会从你的应用程序(application)中脱出控制,但此后该目录所有子文件的病毒扫描都交由应用程序(application)开发者处理。对上传文件的病毒扫描可以之后由额外程序处理,你可以迁移文件到一个允许的文件夹,或通过整合一个杀毒软件(virus scanner)到你的应用程序(application)中。配置一个额外程序或整合杀毒软件(virus scanner)到你的应用程序的详细信息将不记录在本文档范围之内。

上传进度可视化(Watching progress

如果你预期上传一个大文件,并希望良好地反馈(report)给你的用户,文件已经上传了多少。尽管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);

给你提个醒,如果你如上实现了你的第一个进程监听器,那它会向你展示一个陷阱:进程监听器被调用(call)得相当频繁。根据servlet引擎以及其他环境工厂,它可能被任何网络数据包(network packet)调用(call)!换个说法,你的进程监听器可能成为一个性能问题!一个典型的解决方案可能是,降低进度监听器的活跃度。例如,当兆位级发生变化时,你才发送(emit)信息:

// 创建一个进度监听器
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.");
       }
   }
};

接下来是什么(What's next

希望这个页面已经为你提供一个怎么在你的应用程序(application)中使用FileUpload的好办法。这里所介绍的方法(method)的更多信息以及其他有用的方法(method),你应该参考相关的JavaDocs 

这里描述的使用说明应该满足很大部分文件上传的需求。然而,你应该有更复杂的需求(requirement),通过它灵活的自定义能力,FileUpload应该也能帮助你。


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值