使用FILE UPLOAD File Upload有很多不同的使用方式,取决于你的应用程序,最简单的方式是使用一个单独的方法来转换servlet请求,然后处理这些像你的应用发出请求的项。在另一个面,你可能需要决定对哪个项执行完全控制以保存它。比如:你可能会决定把内容保存到数据库中。 这里,我们将描述File Upload的原理,并举例说明一些较为简单--最常使用的模式,关于File Upload的定制,可以在其他其他位置。 File Upload依赖于Commons IO,请确保你的classpath里有对于版本的jar包(相关页面有提及)。 如何工作 一个file upload请求包含一组根据RFC 1867编码的已排序的项目,“基于HTML表单的文件上传”。File Upload能够转换一个请求然后为你的应用程序提供一组单一上传的项目。每个这样的项目都实现了FileItem接口,不太在乎如何具体实现。 这一页介绍了commons fileuplaod库的传统API,传统的API是一个方便的方法。但是,为了最终性能着想,你可能更喜欢使用Streaming API。 每一个文件项都有一些你的应用程序可能感性趣的属性。比如,所有项都有一个名字和内容类型,并且提供了一个输入流去访问它的数据。另一方面,你可能需要使用不同的方式去处理这些项,依据该项是否一个正则的表单元素(数据是否来自于一个传统的文本框或者类似的表单元素),或者一个上传的文件。文件项接口提供了方法去实现这个检测,以及相应的方式访问它们的数据。 Servlet和门户组件 从1.1版开始,FileUpload在servlet和门户组件两种环境里提供文件上传请求。用法在两种环境里差不多,所以这篇文档余下的部分只涉及servlet环境。 如果你正在创建一个门户组件应用,你应该区分下面两点和这篇文档的不同之处。 在任何地方看到ServletFileUpload类的参考文献,请用PortletFileUpload类来代替。 在任何地方看到HttpServletRequest 类的参考文献,请用ActionRequest类来代替。 解析请求 当然,在你操作上传组件之前,你需要解析请求自身。确保请求实际上是一个文件上传请求很简单,File Upload的提供了一个静态方法使它非常简单。 // Check that we have a file upload request 现在我们可以把请求解析成它的组成项。 最简单的情况 最简单的使用情况如下: 只要上传项足够小,那就应该保存在内存里。 大的项应该被写入硬盘的临时文件文件。 非常大的请求应该被阻止上传。 你的应用默认设置可以保存在内存中的最大大小的项目,最大可上传的请求,以及临时存放上传文件的临时目录是可被接受的。 没有比下面更简单的处理请求的方法了: // 为基于硬盘文件的项目集创建一个工厂 FileItemFactory factory = new DiskFileItemFactory(); 以上就是所有需要做的。真的! 请求被解析后,你会提到一些由实现了FileItem接口的项目组成的集合。我们会在下面讨论这此项目。 运用更多控制 如果你的使用情况和最简单的使用情况差不多,如上所述,但是,如果你需要更多的控制的话,可以选择定义上传处理器和项目工厂的行为,这是委简单的。 // 为基于硬盘文件的项目集创建一个工厂 FileItemFactory factory = new DiskFileItemFactory(); // 设置可请求在最大可上传数据量 当然,每个配置都可心独立存在,不过你可以一次性配置工厂的所有配置项,使用一个替代的构造方法可以做到这点,像这样: // 为基于硬盘文件的项目集创建一个工厂 DiskFileItemFactory factory = new DiskFileItemFactory( 如果你需要相比于解析请求更多的控制,像把项目存到其他地方,比如存到数据库,你需要阅读customizing File Upload。 处理上传项目 当完成请求解析后,你会得到一个由你想要处理的文件项组成的集合。大部分情况下,你会想要把使用文件上传的上传的项处理方法区别于和常规表单处理,你需要这样做: // 处理上传项目
Iterator iter = items.iterator(); while (iter.hasNext()) { FileItem item = (FileItem) iter.next(); if (item.isFormField()) { processFormField(item); } else { processUploadedFile(item); } } 对于一个正常的表单项,你只会关心它的name,以及对应的字符串值。和你的预期一样,访问上传项目也非常简单。 // 处理一个正常表单
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(); ... } 对于上传的文件,你通常不会想把它放到内存中,除非它很小,或者你没有其它可替代的方法。恰恰相反,你会把文件当成流来处理,并把整个文件存放到最终位置。File Upload提供了简单的方法来实现这两种处理。 // 处理上传项目
if (writeToFile) { File uploadedFile = new File(...); item.write(uploadedFile); } else { InputStream uploadedStream = item.getInputStream(); ... uploadedStream.close(); } 注意,在FileUpload的默认实现里,如果数据已经在临时文件中,write方法会试图更改存放到指定目的地的文件名。其实数据的复制只会在由于某种原因重命名失败的时候才会成功,或者当数据在内存中已经存在的时候。 如果你需要访问内存中的数据,你仅仅只需要使用get()方法将数据获取并保存到一个字节数组中。 // 处理内存中的上传项
byte[] data = item.get(); ... 资源清理 本节只在你使用DiskFileItem时适用。换句话说,它只适用于你在处理上传项目之前将它们写入临时文件的情况。这些临时文件在不使用(更准确的说,是如果java.io.File的类似实例被垃圾回收了。)的情况下会自动删除。
<web-app>
... <listener> <listener-class> org.apache.commons.fileupload.servlet.FileCleanerCleanup </listener-class> </listener> ... </web-app> 创建一个硬盘文件项工厂 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; } 禁用的清除临时文件 要禁用跟踪临时文件,你要设置FileCleaningTracker为空。因为置空,创建的文件将不会再被跟踪,特别说明,它们也不会再被自动删除。 与病毒扫描器互动 病毒扫描器可能和使用了FileUpload的应用程序运行在同一系统上,这可能会导致web容器执行一些异常的行为。这一节介绍了一些你可能会遇到的异常行为,并且提供了一些方法去处理这些问题。 FileUpload的默认实现会将超出临界值的数据写入硬盘。一旦这个文件关闭,系统上的任何病毒扫描器就是被唤醒并且检测这个文件,可能会隔离这个文件--将它移动到不会引起问题的特定地方。这个,当然,对于程序开发员来说,由于上传的文件项目将不再访问,会让程序员感到奇怪。而另一方面,处于临界值之下的数据会被加载到内存中,因此不会被病毒扫描器检测。这允许病毒以某种形式保存在内存中(尽管它曾经被写入到硬盘中,然后被病毒扫描器扫描并隔离)。 一种常用的解决办法是在系统上设置一个目录来专门保存上传的文件。并且设置病毒扫描器忽略这个目录。这样可以保证应用下的文件不会被分离,但是会分离程序开发人员和病毒扫描的责任关系。可以提供一个扩展的处理程序来执行病毒扫描,将无毒的,被杀过毒的文件移动到特定的位置,或者集成一个病毒扫描系统到应用内部。 关于扩展一个处理以及集成一个扫描系统到应用内部的详细内容超出了本文档的范围。 上传进度 如果希望上传非常大的文件,你需要抽向你的用户提供反馈,已经上传了多少。HTML页面允许通过返回一个multipart/replace响应来实现一个进度条,或其它类似的东西。 可以通过使用一个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引擎和其他环境工厂,它可能会被任何网络数据包调用。换句话说,你的进度条监听器会引起性能问题!可能是一个典型的解决方案,是减少进步条的活动数。比如,只有当上传了1兆字节的时候才反馈给用户: //创建一个进度监听器
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."); } } }; 下一步是什么? 希望这篇文章能够给你在应用中使用File Upload提供了一个好的想法。想要更多关于这篇文章中提到的方法的资料,和其它方法一样,你需要参考JavaDocs。 这里描述的使用应满足大部分的文件上传的需要。然而,如果你有更复杂的要求,FileUpload还能以其灵活的定制帮助你。 |
|