第 16 章 文件上传和下载

文件的上传和下载是项目开发中最常用的功能,例如图片的上传与下载、 邮件附件的上传与下载等。 接下来,本章将对 Spring MVC 环境中文件的上传和下载进行详细的讲解。

文件上传
  • 文件上传概述

多数文件上传都是通过表单形式提交给后台服务器的,因此,要实现文件上传功能,就需要提供一个文件上传的表单,而该表单必须满足以下 3 个条件。

  • form 表单的 method 属性设置为 post。
  • form 表单的 enctype 属性设置为 multipart/form-data。
  • 提供的文件上传输入框。

文件上传表单的示例代码如下。

  

上述代码中,除了满足上传表单所必须的 3 个条件外,在<ínput>元素中还增加了一个 multiple 属性。 该属性是 HTML5 中的新属性,如果使用了该属性,则可以同时选择多个文件进行上传,即可实现多文件上传。
当客户端 form 表单的 enctype 属性为 multipart/form-data 时,浏览器就会采用二进制流的方式来处理表单数据,服务器端就会对文件上传的请求进行解析处理。 Spring MVC 为文件上传提供了直接的支持,这种支持是通过 MultipartResolver (多部件解析器)对象实现的 。 MultipartResolver 是一个接口对象,需要通过它的实现类 CommonsMultipartResolver 来完成文 件上传工作。 在 Spring MVC 中使用 MultipartResolver 对象非常简单,只需要在配置文件中定义 MultipartResolver 接口的 Bean 即可,其具体配置方式如下。

  
  
  
  
  
  ...
  

在上述配置代码中,除配置了 CommonsMultipartResolver 类外,还通过元素配置了编码格式以及允许上传文件的大小。 通过元素可以对文件解析器类 CommonsMultipartResolver 的如下属性进行配置。

  • maxUploadSize: 上传文件最大长度(以字节为单位)。
  • maxlnMemorySize: 缓存中的最大尺寸。
  • defaultEncoding: 默认编码格式。
  • resolveLazily: 推迟文件解析,以便在 Controller 中捕获文件大小异常。

注意:因为 MultipartResolver 接口的实现类 CommonsMultipartResolver 内部是引用 multipartResolver 字符串获取该实现类对象并完成文件解析的,所以在配置 CommonsMultipartResolver 时,必须指定该 Bean 的 id 为MultipartResolver 。
由于 CommonsMultipartResolver 是 Spring MVC 内部通过 Apache Commons FileUpload 技术实现的,所以 Spring MVC 的文件上传还需要依赖 Apache Commons FileUpload 的组件, 即需要导入支持文件上传的相关 JAR 包,具体如下。
• commons-fileupload-1.3.2.jar
• commons-io-2.6.jar
以上两个 JAR 包大家可以通过 Apache 官网地址 " http://commons.apache.org/" 下载(进入该网址后,在 Apache Commons Proper 下方列表 的 Components 列中找到 FileUplod 和 iO ,单击链接后,即可在打开页面找到下载链接)。
当完成页面表单和文件上传解析器的配置后,在 Controller 中编写文件上传的方法即可实现 文件上传。 在 Spring MVC 中,文件上传的方法编写十分简单,其代码如下所示。

@Controller
public class FileUploadController {
  @RequestMapping("/fileUpload") 
  public String handleFormUpload(@RequestParam("name") String name,@RequestParam("filename")  MultipartFile file,...){
      if(!file.isEmpty()){
          //具体的执行办法
          ...
          return "uploadSuccess";
      }
      return "uploadFailure";
  }
}

在上述代码中,包含一个 MultipartFile 接口类型的参数 file ,上传到程序中的文件就是被封装在该参数中的。 org.springframework.web.multipart.MultipartFile 接口中提供了获取上传文件、 文件名称等方法,这些方法及其说明如表所示。

方法 说明
byte[] getBytes() 以字节数组的形式返回文件的内容
String getContentType() 返回文件的内窑类型
InputStream getlnputStream() 读取文内容,返回一个 InputStream 流
String getName() 获取多部件 form 表单的参数名称
String getOriginalFilename() 获取上传文件的初始化名
ong getSize() 获取上传文件的大小,单位是字节
boolean isEmpty() 判断上传的文件是否为空
void transferTo(File file) 将上传文件保存到目标目录下
  • 应用案例一一文件上传

通过上一小节的学习,相信大家对 Spring MVC 中实现文件上传的步骤和配置已经有了一个 大致的了解。 接下来,本小节就通过一个具体的案例来演示文件上传功能的实现,其具体步骤如下。
( 1 )在 Eclipse 中创建一个名为 springmvc06 的 Web 项目,将 Spring MVC 相关 JAR 包以及支持文件上传下载的 JAR 包添加到项目的 lib 目录中,并发布到类路径下。 添加后的 lib 目录如图所示。



( 2 )在 web.xml 文件中,配置 Spring MVC 的前端控制器等信息。
( 3 )在 src 目录下,创建并编写 Spring MVC 的核心配置文件 springmvc-config.xml ,如文件所示。



  
  
  
  
  
  
      
       
      
       
  
  
  
      
      
       

在上述文件中 ,除配置了 Spring MVC 环境需要的组件扫描器、注解驱动和视图解析器外, 还增加了支持文件上传的解析器 CommonsMultipartResolver 的配置。
( 4 ) 在 WebContent 目录下 , 创建一个用于上传文件的页面 fileUpload.jsp ,编辑后文件如下所示。

<%@ page language="java" contentType="text/html; charset=utf-8"
   pageEncoding="utf-8"%>




文件上传



  
  
上传人:
请选择文件:

在上述文件中,编写了一个用于文件上传的 form 表单,该表单可以填写上传人并上传文件。 当单击"上传"按钮时,会先执行 check() 方法来检查上传人文本框和文件选择框中的内容是否为空。 只有填写了上传人并选择了需要上传的文件后,才能正常提交表单;否则表单将不会提交,并给出相应提示信息。 提交表单后,会以 POST 方式提交到一个以 "/fileUpload" 结尾的请求中。
( 5 )在 WEB-INF 目录下,创建 JSP 文件夹,并在文件夹中创建 success.jsp 和 error.jsp 文件,分别在两个文件的元素内编写显示上传成功的信息(如"文件上传成功!" )和显示 上传失败的信息(如"文件上传失败,请重新上传!" )。
( 6 )在 src 目录下,创建一个 com.neuedu.controller 包,在该包下创建一个用于文件上传的控制器类 FileUploadController,编辑后文件如下所示。

package com.neuedu.controller;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import com.sun.org.apache.bcel.internal.classfile.Field;
/**
* 文件上传
*/
@Controller
public class FileUploadController {
  /**
   * 执行文件上传
   */
  @RequestMapping("/fileUpload")
  public String handleFormUpload(@RequestParam("name") String name,@RequestParam("uploadfile") List uploadfile,HttpServletRequest request){
      //判断所上传文件是否存在
      if(!uploadfile.isEmpty() && uploadfile.size() > 0){
          //循环输出上传的文件
          for (MultipartFile file : uploadfile) {
              //获取上传文件的原始名称
              String originalFilename = file.getOriginalFilename();
              //设置上传文件的保存地址目录
              String dirPath = request.getServletContext().getRealPath("/upload/");
              File filePath = new File(dirPath);
              //如果保存文件的地址不存在,就先创建目录
              if(!filePath.exists()){
                  filePath.mkdirs();
              }
              //使用 UUID 重新命名上传的文件名称(上传人 _uuid_ 原始文件名称)
              String newFilename = name + "_"+UUID.randomUUID() + "_" + originalFilename;
              try {
                  file.transferTo(new File(dirPath + newFilename));
              } catch (Exception e) {
                  e.printStackTrace();
                  return "error";
              }
          }
          //跳转到成功页面
          return "success";
      }else{
          return "error";
      }       
  }
}

在文件中,使用注解方式定义了一个控制器类,并在类中定义了执行文件上传的方法 handleFormUpload()。 在 handleFormUpload() 方法参数中使用了List集合类型来接收用户上传的文件,然后判断所上传的文件是否存在。 如果存在,则继续执行上传操作,在通过 MultipartFile 接口的 transferTo() 方法将上传文件保存到用户指定的目录位置后,会跳转到 success.jsp 页面;如果文件不存在或者上传失败,则跳转到 error.jsp 页面。
( 7 )将项目发布到 Tomcat 服务器中并启动,在浏览器中访问地址 http://localhost8080/springmvc06/fileUpload.jsp ,其显示效果如图 16-2 所示。


在图的文件上传页面中,填写上传人并选择所要上传的文件,单击"上传"按钮后就可向后台发送上传请求信息。 这里填写上传人为"小韩",然后选择两张图片后,浏览器的显示效果如图所示。

单击"上传"按钮,程序在正确执行后浏览器就会跳转到 success.Jsp 页面,此时查看项目的发布目录,即可发现在 springmvc06 项目中多了一个 upload 文件夹,该文件夹中的内容如图所示。

从图中可以看出,已经成功上传了两张图片,并且图片的名称为"上传人uuid原始文件名称"的形式。 需要注意的是 , upload 文件夹是在项目的发布路径中,而不是创建的项目所在目录。 如果未更改项目发布路径,则要去工作空间的 metadata 目录中寻找项目发布目录(路径为: workspace.metadata.plugins\org.eclipse.WSt.server.core\tmpl\wtpwebapps\springmvc06\upload );如果将项目的发布路径已更改到 Tomcat 中,则需要在 Tomcat 的 webapps 目录中寻找项目 。

文件下载
  • 实现文件下载

文件下载就是将文件服务器中的文件下载到本机上。 在 Spring MVC 环境中,实现文件下载大致可分为如下两个步骤。
( 1 )在客户端页面使用一个文件下载的超链接,该链接的 href 属性要指定后台文件下载的方法以及文件名(需要先在文件下载目录中添加了一个名称为 "1 .jpg" 的文件),具体代码示例如下。

  
      文件下载
  

( 2 )在后台 Controller 类中,使用 Spring MVC 提供的文件下载方法进行文件下载。 Spring MVC 提供了一个 ResponseEntity 类型的对象,使用它可以很方便地定义返回的 HttpHeaders 对象和 HttpStatus 对象,通过对这两个对象的设置,即可完成下载文件时所需的配置信息。 文件下载的示例代码如下所示。

  @RequestMapping("/download") 
  public ResponseEntity fileDownload(HttpServletRequest request,String filename) throws IOException{
      //指定要下载的文件所在路径
      String path = request.getServletContext().getRealPath("/upload/");
      //创建该文件对象
      File file = new File(path + File.separator + filename);
      //设置响应头
      HttpHeaders headers = new HttpHeaders();
      //通知浏览器以下载的方式打开文件
      headers.setContentDispositionFormData("attachment", filename);
      //定义以流的形式下载返回文件数据
      headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
      //使用Spring MVC框架的ResponseEntity对象封装返回下载数据
      return new ResponseEntity(FileUtils.readFileToByteArray(file),headers,HttpStatus.OK);
  }

在 fileDownload() 方法中,首先根据文件路径和需要下载的文件名来创建文件对象,然后对响应头中文件下载时的打开方式以及下载方式进行了设置,最后返回 ResponseEntity 封装的下载结果对象。
ResponseEntity 对象有些类似前面章节介绍的@ResponseBody 注解,它用于直接返回结果对象。 上面示例中,设置响应头信息中的 MediaType 代表的是 Interner Media Type (即互联网媒体类型),也叫作 MIME 类型, MediaType.APPLlCATION_OCTET_STREAM 的值为 application/octet -stream ,即表示以二进制流的形式下载数据; HttpStatus 类型代表的是 Http 协议中的状态,示例中的 HttpStatus.OK 表示 200 ,即服务器已成功处理了请求。
在 Eclipse 的 WebContent 目录下,创建一个页面文件 download.jsp ,将上述的第 ( 1 ) 步的页面代码编写到 download.jsp 中,然后将第( 2 )步的 fileDownload() 方法编写在 FileUploadController 类中 。 发布项目并启动 Tomcat 服务器,在浏览器中访问地址 http://localhost:8080/springmvc06/download.jsp ,其显示效果如图所示。


单击图中的"文件下载"链接后,会出现下载提示弹窗,如图所示(这里以火狐浏览器为例进行演示)。

选择图中的"保存文件"并单击"确定"按钮后,即可下载该文件。

  • 中文名称的文件下载

虽然在前面小节中,通过 Spring MVC 实现了文件下载功能,但此案例代码只适用于非中文名称的文件下载。 当对中文名称的文件进行下载时,因为各个浏览器内部转码机制的不同,就会出现不同的乱码以及解析异常问题。 例如在文件下载目录中添加一个名称为"壁纸.jpg" 的文件,当通过浏览器下载该文件时,下载弹出窗口的显示如图所示。



从图中可以看出,所要下载的文件名称并不是"壁纸.jpg" ,而是"_.jpg" ,这就表示中文文件名称出现了乱码。 那么我们要如何解决这种乱码问题呢? 为了解决浏览器中文件下载时中文名称的乱码问题,可以在前端页面发送请求前先对中文名进行统一编码,然后在后台控制器类中对文件名称进行相应的转码,其具体实现步骤如下。
( 1 )在下载页面中对中文文件名编码。 可以使用 Servlet API 中提供的 URLEncoder 类中的 encoder(String s, String enc)方法将中文转为 UTF-8 编码。 该方法中第一个参数表示需要转码的字符串,第二个参数表示编码格式,其具体实现方式如文件所示。

<%@ page language="java" contentType="text/html; charset=utf-8"
   pageEncoding="utf-8"%>
<%@ page import="java.net.URLEncoder"%>
"http://www.w3.org/TR/html4/loose.dtd">



下载页面


  ">
      中文名称文件下载
  


( 2 )修改控制器类 FileUploadController 中的 fileDownload() 方法,并增加对文件名进行编码的方法,其代码如下所示。

  @RequestMapping("/download") 
  public ResponseEntity fileDownload(HttpServletRequest request,String filename) throws IOException{
      //指定要下载的文件所在路径
      String path = request.getServletContext().getRealPath("/upload/");
      //创建该文件对象
      File file = new File(path + File.separator + filename);
      //对文件名编码,防止中文文件乱码
      filename = this.getFilename(request,filename);
      //设置响应头
      HttpHeaders headers = new HttpHeaders();
      //通知浏览器以下载的方式打开文件
      headers.setContentDispositionFormData("attachment", filename);
      //定义以流的形式下载返回文件数据
      headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
      //使用Spring MVC框架的ResponseEntity对象封装返回下载数据
      return new ResponseEntity(FileUtils.readFileToByteArray(file),headers,HttpStatus.OK);
  }
  /**
   * 根据浏览器的不同进行编码设置,返回编码后的文件名
   */
  private String getFilename(HttpServletRequest request, String filename) throws UnsupportedEncodingException {
      //IE不同版本的User-Agent中出现的关键词
      String[] IEBrowserKeyWords = {"MS1E", "Trident", "Edge"}; 
      //获取请求头代理信息
      String userAgent = request.getHeader("User-Agent"); 
      for (String keyWord : IEBrowserKeyWords) {
          if(userAgent.contains(keyWord)){
              //IE内核浏览器,统一为UTF-8编码显示
              return URLEncoder.encode(filename, "UTF-8") ; 
          }
      }
      //火狐等其他浏览器统一为ISO-8859-1编码显示
      return new String(filename.getBytes("UTF-8"), "ISO-8859-1"); 
  }

在方法 getFilename() 中,由于 IE 浏览器在文件编码上与其他浏览器的方式不同,所以在中文编码设置上 IE 浏览器设置为 UTF-8 编码,而火狐等其他浏览器设置为 ISO-8859-1 编码。 另外由于不同版本的 IE 浏览器,请求代理 User-Agent 中的关键字也略有不同,所以在判断 IE 浏览器时,需要特别注意 User-Agent 中的关键字。
再次进行中文名的文件下载测试,并在 IE 和火狐浏览器中分别单击文件下载链接后,两个浏览器的显示效果如图所示。




从图中的显示效果可以看出,所下载的文件已在两个浏览器中正确显示出了中文名称。

本章小结

本章主要对 Spring MVC 环境下的文件上传和下载进行了详细讲解。 首先讲解了如何实现文件上传,并通过一个应用案例来演示文件上传功能的实现;然后讲解了非中文名称文件下载的实现过程,以及中文名称文件下载的实现过程。 通过本章的学习,大家可以学会如何在 Spring MVC 环境下进行文件上传和下载,并能够掌握中文名称文件下载时乱码的解决方案。

你可能感兴趣的:(第 16 章 文件上传和下载)