7.1 使用上传框架实现文件上传
7.1.1 上传框架介绍
文件上传和下载,是一个Web开发应用中常用的功能。在开发中会经常需要实现文件的上传和下载。
可以通过获得HTTP请求的输入流,然后通过输出流将数据保存到文件中。不过这里有很多问题,比如说要实现非文本文件的上传,则需要通过自己编写代码来实现,这是非常困难的事情。
为什么选择使用Struts 2框架呢,这是因为通过它来构建Web应用会变得更加简单、方便。同样可以使用一些开源的上传框架来实现文件上传,这样可以很轻松地完成应用的需求。
不过其底层的实现机制也要花一点时间去了解。虽然不用去手动实现该框架,但是要能够对该框架实现原理有所掌握。
下面来看看目前有哪些优秀的上传框架。目前比较流行的有Common-FileUpload框架、COS框架,这两个框架都出生豪门,其上传功能实现得非常好。而且代码封装得特别好,开发人员只需创建几个对象并调用其方法就能实现文件上传。
Struts 2并没有提供用来实现文件上传的组件,而是通过调用这些上传框架来实现的上传。使用Struts 2框架的好处在于它对这些上传框架进行了再一次的封装,从而进一步地简化了文件上传这一功能。
首先不使用Sturts 2框架,而是单纯地使用这些上传框架来实现文件上传。下面首先来看如何通过Common-FileUpload框架实现文件上传。
7.1.2 下载并安装Common-FileUpload框架
首先还是要去下载Common-FileUpload框架。Common-FileUpload框架是Apache开源组织下的一个项目。登录Apache的官方站点并找到jakarta项目下的Commons项目,单击此链接进入Commons项目首页,如图7.1所示。
图7.1 Commons项目首页
Commons项目中有许多的小项目,找到其中的Commons-FileUpload项目和Commons-IO项目,单击其链接进行下载。
要使用FileUpload项目,则Commons-IO项目是必备的。因为IO项目用来解析表单域等,所以要使用Commons-FileUpload项目,同样必须安装IO项目。这里选择目前的最新版下载,其中Commons-FileUpload项目的最新版本为1.2.1, Commons-IO的最新版本为1.4。
下载完成后,得到两个压缩文件包,分别为“commons-fileupload-1.2.1-bin.zip”和“commons-io-1.4-bin.zip”。解压“commons-fileupload-1.2.1-bin.zip”文件,如图7.2所示。解压“commons-io-1.4-bin.zip”文件,如图7.3所示。
图7.2 “commons-fileupload-1.2.1-bin.zip”文件
图7.3 “commons-io-1.4-bin.zip”文件
要安装Common-FileUpload框架,只需将“commons-fileupload-1.2.1-bin\lib”路径下的“commons-fileupload-1.2.1.jar”库以及“commons-io-1.4-bin”目录下的“commons-io-1.4.jar”文件复制到Web应用中WEB-INF下的lib目录中。
其中javadoc文件夹中包含该框架的API文档,sources文件夹中包含该框架的源代码。
7.1.3 通过Common-FileUpload框架实现文件上传
现在来看如何通过Common-FileUpload框架实现文件上传。
步骤如下。
(1)新建用户输入页“upload.jsp”。该页面仅仅包含一个表单,用来输入用户名以及选择用来上传的文件。这里需要注意两点:第一点是在于form表单的method属性必须为post,即设置表单的提交方式为post方式;第二点必须设置form表单的enctype属性为multipart/form-data,这样才会以二进制流的方式来处理表单数据,从而完成文件的上传。enctype属性的默认值为application/x-www-form-urlencoded,这样的编码格式会将表单处理成URL编码格式,代码如下所示。
<%@ page language="java" pageEncoding="gb2312"%> <html> <head> <title>文件上传</title> </head> <center> <h1>通过Common-FileUpload框架完成上传</h1> <form action="UploadServlet" method="post" enctype="multipart/form-data"> <table> <tr> <td>用户名:</td> <td><input type="text" name="username" ></td> </tr> <tr> <td>上传文件:</td> <td><input type="file" name="myFile"></td> </tr> <tr> <td><input type="submit" value="上传"></td> <td><input type="reset"></td> </tr> </table> </form> </center> <body> </body> </html>
(2)新建Servlet用来处理上传。重点在于对表单域的判断,判断该表单域是一个普通的表单域还是文件域。如果是普通表单域则取得其表单域的name属性值和value属性,这个相当于传递参数中的参数与参数值。如果是文件表单域则取得该文件表单域的name属性值和上传文件名,并将该文件保存到设置的上传文件目录中。完成文件上传后,跳转页面到结果输出页,代码如下所示。
package net.hncu.upload; import java.io.File; import java.io.IOException; import java.util.List; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; public class UploadServlet extends HttpServlet { public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //新建上传工厂 DiskFileItemFactory factory = new DiskFileItemFactory(); //设置文件大小限制 //如果小于该文件限制,则文件直接保存在内存中如何保存到目标文件夹中 //如果大于该文件限制,则文件将保存到设置的临时目录中 factory.setSizeThreshold(1024 * 1024); //设置临时目录 factory.setRepository(new File(getServletContext().getRealPath("/temp"))); //实例化一个ServletFileUpload对象用来上传 ServletFileUpload sfu = new ServletFileUpload(factory); //得到所有的表单项 List<FileItem> all = null; try { all = sfu.parseRequest(request); } catch (FileUploadException e) { e.printStackTrace(); } for(FileItem item : all) { //如果是普通表单字段 if(item.isFormField()) { //获得该字段名称 String name = item.getFieldName(); //获得该字段值 String value = item.getString("gbk"); //将值设置到request属性范围中 request.setAttribute(name, value); } //如果为文件域 else { //取得文件域字段名 String name = item.getFieldName(); //取得文件名 String value = item.getName(); //截取文件名 int begin = value.lastIndexOf("\\"); value = value.substring(begin + 1); //将值设置到request属性范围中 request.setAttribute(name, value); //设置上传文件目录 String uploadPath = getServletContext().getRealPath("/upload"); //写入文件 try { item.write(new File(uploadPath, value)); } catch (Exception e) { e.printStackTrace(); } } } //上传完成后执行跳转 request.getRequestDispatcher("/result.jsp").forward(request, response); } }
(3)配置文件上传Servlet,代码如下所示。
<? xml version="1.0" encoding="UTF-8"? > <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <servlet> <servlet-name>UploadServlet</servlet-name> <servlet-class>net.hncu.upload.UploadServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>UploadServlet</servlet-name> <url-pattern>/UploadServlet</url-pattern> </servlet-mapping> </web-app>
(4)新建结果输出页“result.jsp”。在该页面中输出用户的用户名以及上传的文件名,代码如下所示。
<%@ page language="java" pageEncoding="gb2312"%> <html> <head> <title>文件上传</title> </head> <center> <h1>通过Common-FileUpload框架完成上传</h1> 用户名:${requestScope.username}<br> 上传文件名:${requestScope.myFile } </center> <body> </body> </html>
7.1.4 测试文件上传
现在来测试是否能真正地完成文件上传。首先打开用户输入页,如图7.4所示。用户名可以填写用户自己的名字,上传文件选择一个文件比较小点的文件,单击“上传”按钮进行上传。
页面跳转到结果输出页。页面中显示了用户名以及上传的文件名,如图7.5所示。
图7.4 用户输入页
图7.5 结果输出页
下面打开文件上传目录upload,可以在该目录中看到了刚才上传的文件,如图7.6所示。
图7.6 上传目录upload
这里只所以选择文件大小比较小的文件来上传,只是为了演示大文件上传时的情况。因为在Servlet中配置了文件大小限制为1MB,如果大于该限制,则文件将保存到设置的临时目录中。为了能够看到临时目录中的文件,这里选择一个比较小的文件来上传。在上传过程打开临时目录temp,从该目录中可以看到一个临时文件,如图7.7所示。
当文件上传完成后,该临时目录下的文件会自动删除,如图7.8所示。
图7.7 临时目录temp(1)
图7.8 临时目录temp(2)
这样就可以上传任意大小的文件了,但是要注意文件上传比较耗费系统资源,还会有安全问题。后面将介绍如何配置文件上传大小的限制以及上传文件类型的限制。
7.1.5 上传多个文件
下面来看如何同时上传多个文件。在Servlet中使用了循环遍历所有的表单域,所以只要在用户输入页中增加文件域就可以了,修改代码如下所示。
<form action="UploadServlet" method="post" enctype="multipart/form-data"> <table> <tr> <td>用户名:</td> <td><input type="text" name="username" ></td> </tr> <tr> <td>上传文件1:</td> <td><input type="file" name="myFile1"></td> </tr> <tr> <td>上传文件2:</td> <td><input type="file" name="myFile2"></td> </tr> <tr> <td><input type="submit" value="上传"></td> <td><input type="reset"></td> </tr> </table> </form>
同样在结果页中增加对上传文件名的输出,修改代码如下所示。
<%@ page language="java" pageEncoding="gb2312"%> <html> <head> <title>文件上传</title> </head> <center> <h1>通过Common-FileUpload框架完成上传</h1> 用户名:${requestScope.username}<br> 上传文件名1:${requestScope.myFile1 }<br> 上传文件名2:${requestScope.myFile2} </center> <body> </body> </html>
7.1.6 测试上传多个文件
打开用户输入页,填写用户名并选择两个文件进行上传,如图7.9所示。
页面跳转到结果输出页。页面中显示了用户名以及上传的文件名,如图7.10所示。
图7.9 用户输入页
图7.10 结果输出页
下面打开文件上传目录upload,在该目录中可以看到刚才上传的两个文件,如图7.11所示。
这里还有一点需要注意,如果现在只上传一个文件则会出现异常,如图7.12所示。
图7.11 上传目录upload
图7.12 Tomcat服务器控制台输出
因为没有选择文件,所以这里肯定找不到这个文件。这种处理的问题方法是,只用判断用户在该文件域中是否选择了文件就行了,如果没有选择文件就退出循环,不让其进行文件上传就行了。
7.1.7 下载并安装COS框架
下面来看另一个上传框架—COS框架。COS框架是oreilly组织下的一个小项目。要获得它首先要登录其官方站点“http://www.servlets.com”,单击右侧导航栏中的“com.oreilly.servlet”链接进入COS项目首页,如图7.13所示。
从项目首页中看到目前COS的最新版本为05Nov2002,单击其下载链接进行下载。下载完成后,得到一个名为“cos-05Nov2002.zip”的压缩文件包,如图7.14所示。
图7.13 COS项目首页
图7.14 “cos-05Nov2002.zip”文件
要安装COS框架,只需将“cos-05Nov2002\lib”路径下的“cos.jar”库文件复制到Web应用中的WEB-INF下的lib目录中。
COS项目也是开源的,在该文件中同样提供了COS的API文档以及源代码。API文档也是非常重要的,方便在实际开发中随时要查阅。
7.1.8 通过COS框架实现文件上传
现在来看如何通过COS框架来实现文件上传。首先将用户输入页和结果页稍微做些修改,将如下代码:
<h1>通过Common-FileUpload框架完成上传</h1>
替换成:
<h1>通过COS框架完成上传</h1>
COS框架的核心类为MultipartParser,这个类负责解析HTTP请求,同时还可以用来设置上传文件的最大值。可以通过MultipartParser的readNextPart()方法来获得所有的表单域。COS使用Part实例来表示所有的表单域,也就是说不管是普通表单域还是文件域,其类型都是Part。
可以调用Part实例的isParam()方法来判断该表单域是不是普通表单域,同样也可以调用其isFile()来判断该表单域是不是文件域。
下面就来新建一个Servlet用来处理上传。重点在于对表单域的判断,判断该表单域是一个普通的表单域还是文件域。如果是普通表单域,则取得其表单域的name属性值和value属性。如果是文件表单域,则取得该文件表单域的name属性值和上传文件名,并将该文件保存到设置的上传文件目录中。完成文件上传后,跳转页面到结果输出页,代码如下所示。
package net.hncu.upload; import java.io.File; import java.io.IOException; import java.util.List; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.oreilly.servlet.multipart.FilePart; import com.oreilly.servlet.multipart.MultipartParser; import com.oreilly.servlet.multipart.ParamPart; import com.oreilly.servlet.multipart.Part; public class UploadServlet extends HttpServlet { public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // MultipartParser用来解析HTTP请求,并设置上传文件大小最大值 MultipartParser mp = new MultipartParser(request,1024 * 1024 * 20); // 所有的表单字段都是一个Part实例 Part part; // 遍历循环每个表单字段,使用MultipartParser类中的readNextPart方法来获得part实例 while((part = mp.readNextPart()) ! = null) { // 如果是普通表单字段 if(part.isParam()) { // 获得该字段名称 String name = part.getName(); // 强制转换 ParamPart pmp = (ParamPart)part; // 获得该字段值 String value = pmp.getStringValue("GBK"); // 将值设置到request属性范围中 request.setAttribute(name, value); } // 如果为文件域 else if(part.isFile()) { // 取得文件域字段名 String name = part.getName(); // 强制转换 FilePart flp = (FilePart)part; // 获得文件名,并将其编码格式转换成gbk String value = new String (flp.getFileName().getBytes("ISO-8859-1"), "gbk"); // 将值设置到request属性范围中 request.setAttribute(name, value); // 设置上传文件目录 String uploadPath = getServletContext().getRealPath("/upload"); // 写入文件 try { flp.writeTo(new File(uploadPath, value)); } catch (Exception e) { e.printStackTrace(); } } } // 上传完成后执行跳转 request.getRequestDispatcher("/result.jsp").forward(request, response); } }
Servlet编写好后,别忘了在“web.xml”文件中配置该Servlet,这里使用和前面一样的配置。
7.1.9 测试使用COS框架实现文件上传
打开用户输入页,填写用户名并选择两个文件进行上传,如图7.15所示。页面跳转到结果输出页。页面中显示了用户名以及上传的文件名,如图7.16所示。
图7.15 用户输入页
图7.16 结果输出页
下面打开文件上传目录upload,在该目录中可以看到刚才上传的两个文件,如图7.17所示。
图7.17 上传目录upload
这里还要注意一点,因为设置了上传文件大小最大值,所以如果上传的文件大于该最大值将会抛出异常,如图7.18所示。
这种直接抛出异常对于用户来说非常不友好,应该提醒用户上传的文件太大。至于如何实现输出错误提示,这部分留到后面介绍基于Struts 2完成文件上传时完成。
图7.18 文件过大抛出异常