SpringMVC之文件上传和下载

SpringMVC实现文件的上传和下载 相对于JavaWeb阶段咱们使用过servlet实现文件上传和下载操做;而SpringMVC实现了对上传操做的直接支持,提供了multipart解析器。MultipartFile提供了一些对文件操做的方法,使得文件上传变得更简单。不管上传仍是下载都是进行二进制流的转换,下面咱们以案例的形式了解一下如何使用SpringMVC实现文件的上传操做。html

<!--more-->java

文件上传

准备

了解

  1. 文件上传咱们首先要考虑的就是把文件上传到哪里?是上传到工程目录下,仍是上传到本地磁盘中?
  2. 由于上传的文件通常都是二进制文件,因此咱们须要经过某种方式对表单提交进行编码。经过将enctype设置为multipart/form-data,每一个输入域都将做为POST请求的不一样部分进行提交(默认提交的表单中数据存储格式是名字-值,显然是不适合相似文件上传那种二进制数据的)。
  3. Spring提供了对multipart数据的解析器CommonsMultipartResolverMultipartResolver接口的实现类),可是这个解析器是基于Apache Commons FileUpload技术的,因此须要commons-filrUpload.jar支持。

配置

除了咱们以前使用的Spring以及SpringMVC的先关jar依赖包,还须要导入如下jar文件:git

commons-fileupload.jar
commons-io.jar

实例

首先咱们看一下项目结构:github

注意:这里我使用的配置是:IDEA + tomcat + Maven,对于maven项目咱们要清楚项目编译后的文件都放在target目录下。web

  1. 修改咱们的save.jsp
<%@ page isELIgnored="false" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    String path = request.getContextPath();
    String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path;
%>
<html>
<head>
    <title>Title</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h2>表单</h2>
<form action="<%=basePath%>/user/save" method="post" enctype="multipart/form-data">
    username: <input type="text" name="username"/><br/>
    password: <input type="password" name="password"/><br/>
    profile image:<input type="file" name="image"/><br/>
    <input type="submit" value="提交"/>
</form>
</body>
</html>

解释: 注意在上面<%%>中的Java代码是获取本项目的相对路径,至关于<%=pageContext.request.ContextPath%>。接下来咱们将表单<form>标签中设置属性enctype="multipart/form-data"spring

  1. Controller
//保存用户
@RequestMapping(value = "/save", method = RequestMethod.POST)
public String save(@RequestParam String username, @RequestParam String password, @RequestParam(value="image",required = false)MultipartFile image, HttpServletRequest request, Model model) {

    //获取文件在服务器上的储存位置
    String path = request.getSession().getServletContext().getRealPath("resources/upload");
    File filePath = new File(path);
    System.out.println("文件保存路径:" + path);
    if (!filePath.exists() && !filePath.isDirectory()) {
        System.out.println("目录不存在,建立目录:" + filePath);
        filePath.mkdir();
    }

    //获取原始文件名称
    String originalFileName = image.getOriginalFilename();
    System.out.println("原始文件名称:" + originalFileName);

    //获取文件类型,以最后一个`.`做为标识
    String type = originalFileName.substring(originalFileName.lastIndexOf(".") + 1);
    System.out.println("文件类型:" + type);

    //设置文件新名字
    String fileName = System.currentTimeMillis() + "." + type;
    System.out.println("文件新名称:" + fileName);
    //在指定路径建立一个文件
    File targetFile = new File(path, fileName);

    //将文件保存到服务器指定位置
    try {
        image.transferTo(targetFile);
        model.addAttribute("message", "保存数据成功");
        userService.save(username,password,"resources/upload/" + fileName);
        return "view/success";
    } catch (IOException e) {
        System.out.println("保存文件错误...");
        e.printStackTrace();
    }
    return "view/error";
}

Controller层就比较复杂了,由于对于本案例而言文件上传操做的主要代码都在Controller层,而Service和Dao层仅是执行保存操做。咱们首先了解一下文件操做的API方法:数据库

  1. request.getSession().getServletContext().getRealPath(String s):获取本项目下指定目录的绝对路径。
  2. MultipartFile.getOriginalFilename():获取被MultipartFile绑定的上传参数的原始名称
  3. System.currentTimeMillis():获取自1970年1月1日0时起到如今时间的毫秒数。
  4. MultipartFile.transferTo(): 在指定的磁盘路径下生成一个新的文件。
  • jsp中的form表单将数据提交到这个映射方法,那么咱们就要接受jsp中传递的参数,要注意的就是绑定image参数是MultipartFile数据类型。
  • 下面就要定义一个上传文件的保存目录,本例中使用request.getSession().getServletContext().getRealPath()获取到的是本项目的绝对路径,而getRealPath("resources/upload")是针对此项目的相对路径,如此时的文件保存路径实际上是:/Users/ty/Documents/Java/Learn/ssm/target/TyCoding/resources/upload
  • 若是指定路径下的文件夹存在,就将上传的文件写入进这个文件夹中,若是此文件夹不存在,就调用mkdir()方法建立此文件夹。
  • 上述一切都准备就绪,最后会调用Service层的save()方法将表单数据保存到数据库中。(**注意:**在调用的save()方法中咱们会看到实际写入进数据库中的上传文件的路径其实仅是相对此项目的相对路径(即相对于webapp)。
  1. 最后咱们要在Spring配置文件中注册这个multipart解析器:
<!-- 配置支持文件上传 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" p:maxUploadSize="500000"/>

maxUploadSize是设置上传文件的大小,能够更改。浏览器

下面咱们看一下实际效果: 点击提交按钮,访问后台映射方法: 能够看到已经获取到表中的数据,并准备调用Service层保存数据,那么咱们看一下最后的Dao层将数据存入进数据库的方法:tomcat

public class UserDaoImp extends JdbcDaoSupport implements UserDao {

    //保存数据
    public void save(String username, String password, String image) {
        //使用Spring提供的JDBC模板能够直接执行SQL语句
        this.getJdbcTemplate().update("insert into user(id,username,password,image) values(?,?,?,?)",null,username,password,image);
    }
}

最后数据保存成功,跳转到成功页面: 看一下此文件在工程中的位置: 看一下储存到数据库中数据: 服务器

  1. 最后咱们看一下经过SQL语句:select * from user,将查询到的数据回显到页面上: list.jsp
<table border="1">
    <tr>
        <td>id</td>
        <td>username</td>
        <td>password</td>
        <td>avatar</td>
    </tr>
    <c:forEach items="${userList}" var="user">
        <tr>
            <td>${user.id}</td>
            <td>${user.username}</td>
            <td>${user.password}</td>
            <td><img src="<%=basePath%>/${user.image}" width="100" height="80"></td>
        </tr>
    </c:forEach>
</table>

答疑:

  1. 咱们设置的getRealPath("resources/upload")不应是相对于工程的路径吗?(也就是在webapp下)。为何上传的文件会保存到target目录下? 答:咱们要明白Tomcat下运行的项目实际是编译后的class文件,即此maven项目中的target文件夹中的数据。那么咱们设置的文件上传路径实际上是在此编译后的文件夹中的相对路径(TyCoding是我设置的artifact id
  2. 为何咱们保存到数据中的路径是项目的相对路径? 答:要知道在Tomcat下运行项目,咱们访问的图片路径是须要在localhost:8080下的路径,也就是当前项目的相对路径,因此咱们若是保存一个带盘符的绝对路径是确定不能访问到的。

文件下载

准备

了解

文件上传咱们这里设计的思路是:在页面设置一个可点击的链接(好比<a>),点击便可下载,而咱们须要在其href属性中拼接要下载的文件的名称,而后经过这个请求路径,Controller层的映射方法接收到你要下载的文件名称,而后根据指定的下载文件的路径查询到这个文件的名称,而后将文件转换成二进制流,而后让客户端读取这个二进制流写入到本机中从而实现下载。

  1. ResponseEntity<byte[]>: SpringMVC提供的用于实现响应头、文件数据(以字节储存)、状态封装都一块儿返回给浏览器实现文件的下载。
  2. header.setContentDispositionFormData(): 告诉浏览器要以指定的数据类型打开这个文件
  3. FileUtils.readFileToByteArray(): 使用FileUtils工具类强制将指定文件数据转换成byte字节流的形式。

实例

  1. 首先咱们更改上面的save.jsp页面,新增一个下载的链接:
<h2>文件下载</h2>
<a href="<%=basePath%>/user/download?fileName=图片.jpg">点击我下载图片</a>

如上所示,咱们在请求路径中拼接了fileName参数值是图片.jpg那么后台接收到这个参数值,就会查询指定位置的文件。

  1. 而后看一下Controller的请求
//文件下载
@RequestMapping("/download")
public ResponseEntity<byte[]> download(HttpServletRequest request, @RequestParam(value="fileName",required = false) String fileName) throws Exception {
    try{
        //下载路径
        String path = request.getServletContext().getRealPath("/resources/upload/");
        File file = new File(path + File.separator + fileName);
        HttpHeaders headers = new HttpHeaders();
        //解决文件名中文乱码问题
        String downloadFileName = new String(fileName.getBytes("UTF-8"),"iso-8859-1");
        //告诉浏览器以"attachment"方式打开文件
        headers.setContentDispositionFormData("attachment",downloadFileName);
        //设置请求头的媒体格式类型为 application/octet-stream(二进制流数据)
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(file),headers,HttpStatus.CREATED);
    } catch(Exception e){
        e.printStackTrace();
        System.out.println("文件下载出错...");
        return null;
    }
}

首先要指定映射方法的返回数据类型是ResponseEntity<byte[]>,首先要规定一个下载文件的目录路径,须要下载文件时,就会中这个路径中查询须要下载的文件,若是找不到指定文件,那么就进catch里面。 File.separator用来分隔同一个路径字符串中的目录,至关于/。找到了指定路径下的文件后,须要解决中文乱码问题,而后告诉浏览器要以attachment的方式打开这个文件。 最后这只ContentType媒体格式类型,最后使用FileUtilsreadFileToByteArray将文件数据转换成二进制字节流,连同设置好的响应数据格式一同返回给浏览器,实现文件的下载。

最后看一下实际的效果:

<br/>

交流

若是你们有兴趣,欢迎你们加入个人Java交流群:671017003 ,一块儿交流学习Java技术。博主目前一直在自学JAVA中,技术有限,若是能够,会尽力给你们提供一些帮助,或是一些学习方法,固然群里的大佬都会积极给新手答疑的。因此,别犹豫,快来加入咱们吧!

<br/>

联系

If you have some questions after you see this article, you can contact me or you can find some info by clicking these links.