零基础学Struts
上QQ阅读APP看书,第一时间看更新

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 文件过大抛出异常