随笔-0  评论-0  文章-2  trackbacks-0

声明

英文原版版权© 2002-2008 The Apache Software Foundation

本文非官方中文版,是本文翻译者自愿翻译,与The Apache Software Foundation无任何关系

当前Commons FileUpload版本为1.2.2


Copyright 2010  王丛琳 放弃本翻译版本除署名权外的一切权利。


翻译完成时间122

如您对翻译有异议,或发现翻译错误,敬请不吝赐教。Emailcon.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同时支持servletportlet环境。由于在这两种环境下使用方法几乎相同,所以本文只提到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);

    }

禁止清理临时文件

设置 FileCleaningTrackernull,就可以禁用跟踪(译者注:相对跟踪,本人感觉记录更合适)临时文件。以后,创建的文件将不再被跟踪。并且,它们将不再被自动删除。

 

关于杀毒软件

在杀毒软件和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应该仍然能通过灵活的定制能力帮助你。


posted on 2010-01-22 09:53 明明明 阅读(348) 评论(0)  编辑  收藏

只有注册用户登录后才能发表评论。


网站导航: