SpringMVC 文件上传下载

时间:2022-04-30 22:17:57

@

文件上传

文件上传是项目开发中最常见的功能。为了能上传文件,必须将表单的method设置为POST,并将enctype设置为multipart/form-data。只有在这样的情况下,浏览器才会把用户选择的文件以二进制数据发送给服务器。

一旦设置了enctype为multipart/form-data,浏览器即会采用二进制流的方式来处理表单数据,而对于文件上传的处理则涉及在服务器端解析原始的HTTP响应。在2003年,Apache Software Foundation发布了开源的Commons FileUpload组件,其很快成为Servlet/JSP程序员上传文件的最佳选择。

Servlet3.0规范已经提供方法来处理文件上传,但这种上传需要在Servlet中完成。而Spring MVC则提供了更简单的封装。

Spring MVC为文件上传提供了直接的支持,这种支持是用即插即用的MultipartResolver实现的。Spring MVC使用Apache Commons FileUpload技术实现了一个MultipartResolver实现类:CommonsMultipartResolver。因此,SpringMVC的文件上传还需要依赖Apache Commons FileUpload的组件。

MultipartFile对象

Spring MVC会将上传的文件绑定到MultipartFile对象中。MultipartFile提供了获取上传文件内容、文件名等方法。通过transferTo()方法还可以将文件存储到硬件中,MultipartFile对象中的常用方法如下:

  • byte[] getBytes():获取文件数据
  • String getContentType[]:获取文件MIME类型,如image/jpeg等
  • InputStream getInputStream():获取文件流
  • String getName():获取表单中文件组件的名字
  • String getOriginalFilename():获取上传文件的原名
  • Long getSize():获取文件的字节大小,单位为byte
  • boolean isEmpty():是否有上传文件
  • void transferTo(File dest):将上传文件保存到一个目录文件中

文件下载

在页面给出了一个超链接,该链接href的属性等于要下载文件的文件名,就可以实现文件下载了。

接收页面传递的文件名filename后,使用Apache Commons FileUpload组件的FileUtils读取项目的上传文件,并将其构建成ResponseEntity对象返回客户端下载。

使用ResponseEntity对象,可以很方便的定义返回的HttpHeaders和HttpStatus。上面代码中的MediaType,代表的是Internet Media Type,即互联网媒体类型,也叫做MIME类型。在Http协议消息头中,使用Content-Type来表示具体请求中的媒体类型信息。HttpStatus类型代表的是Http协议中的状态。有关MediaType和HttpStatus类可以参考Spring MVC的API文档。

上传下载示例

pom.xml增加

<!-- 文件上传组件 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3</version>
</dependency>

创建uploadForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>文件上传</title>
</head>
<body>
<h2>文件上传</h2>
<form action="${pageContext.request.contextPath}/mvc/upload" enctype="multipart/form-data" method="post">
<table>
<tr>
<td>文件描述:</td>
<td><input type="text" name="description"></td>
</tr>
<tr>
<td>请选择文件:</td>
<td><input type="file" name="file"></td>
</tr>
<tr>
<td><input type="submit" value="上传"></td>
</tr>
</table>
</form>
</body>
</html>

创建uploadForm2.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>用户注册</title>
</head>
<body>
<h2>用户注册</h2>
<form action="${pageContext.request.contextPath}/mvc/upload2" enctype="multipart/form-data" method="post">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="name"></td>
</tr>
<tr>
<td>请上传头像:</td>
<td><input type="file" name="image"></td>
</tr>
<tr>
<td><input type="submit" value="注册"></td>
</tr>
</table>
</form>
</body>
</html>

创建userInfo.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>文件下载</title>
</head>
<body>
<h3>文件下载</h3>
<a href="download?filename=${requestScope.user.image.originalFilename}"> ${requestScope.user.image.originalFilename }</a>
<a href="download?filename=${user.name}"> ${user.name}</a>
</body>
</html>

springmvc-servlet.xml添加

SpringMVC上下文中默认没有装配MultipartResolver,因此默认情况下其不能处理文件上传工作。如果想使用Spring的文件上传功能,则需要在上下文中配置MultipartResolver

<!-- 文件上传的配置 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="1048576" /><!-- 上传文件大小上限,单位为字节(1MB) -->
<property name="defaultEncoding" value="UTF-8" /><!-- 请求的编码格式,必须和jSP的pageEncoding属性一致,以便正确读取表单的内容,默认为ISO-8859-1 -->
</bean>

创建User2.java对象

使用对象接受接收上传文件,必须要实现序列化接口

package com.xc.entity;

import java.io.Serializable;

import org.springframework.web.multipart.MultipartFile;

public class User2 implements Serializable {

	private static final long serialVersionUID = 1L;
private String name;
private MultipartFile image; public User2() {
super();
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public MultipartFile getImage() {
return image;
} public void setImage(MultipartFile image) {
this.image = image;
} }

创建UploadController.java

package com.xc.controller;

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID; import javax.servlet.http.HttpServletRequest; import org.apache.commons.io.FileUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile; import com.xc.entity.User2; @Controller
@RequestMapping("/mvc")
public class UploadController { // 上传文件会自动绑定到MultipartFile中
@RequestMapping(value = "/upload", method = RequestMethod.POST)
public String upload(HttpServletRequest request, @RequestParam("description") String description,
@RequestParam("file") MultipartFile multipartFile) throws Exception { System.out.println(description);
if (!multipartFile.isEmpty()) {// 是否有上传文件
// 上传文件路径
// String path = request.getServletContext().getRealPath("/upload/");
// 得到上传文件的保存目录,将上传的文件存放于WEB-INF目录下,不允许外界直接访问,保证上传文件的安全
String path = request.getServletContext().getRealPath("/WEB-INF/upload");
System.out.println(path);
String filename = multipartFile.getOriginalFilename();// 获取上传文件的原名
File filepath = new File(path, filename);
System.out.println(filepath);
// 判断路径是否存在,如果不存在就创建一个
if (!filepath.getParentFile().exists()) {
filepath.getParentFile().mkdirs();
}
multipartFile.transferTo(new File(path + File.separator + filename));// 将上传文件保存到一个目标文件当中
return "success";
} else {
return "error";
}
} @RequestMapping(value = "/upload2")
public String register(HttpServletRequest request, @ModelAttribute User2 user, Model model) throws Exception {
System.out.println(user.getName());
// 如果文件不为空,写入上传路径
if (!user.getImage().isEmpty()) {
// 上传文件路径
String path = request.getServletContext().getRealPath("/WEB-INF/upload");
// 上传文件名
String filename = user.getImage().getOriginalFilename(); String fileExtName = filename.substring(filename.lastIndexOf(".") + 1);// 得到上传文件的扩展名
System.out.println("上传文件的扩展名:" + fileExtName);
String filenamePre = filename.substring(0, filename.lastIndexOf("."));
System.out.println("上传文件的文件名:" + filenamePre); String saveFilename = makeFileName(filenamePre, fileExtName);
System.out.println("saveFilename:" + saveFilename);
Map<String, Object> pathMap = makePath(saveFilename, path);
String dir1 = pathMap.get("dir1").toString();
String dir2 = pathMap.get("dir2").toString();
System.out.println("pathMap:" + pathMap);
String realSavePath = pathMap.get("dir").toString();
System.out.println("realSavePath:" + realSavePath);
File filepath = new File(realSavePath, saveFilename);
// 判断路径是否存在,如果不存在就创建一个
if (!filepath.getParentFile().exists()) {
filepath.getParentFile().mkdirs();
} // 将上传文件保存到一个目标文件当中
user.getImage().transferTo(new File(realSavePath + File.separator + saveFilename));
user.setName("" + dir1 + "/" + dir2 + "/" + saveFilename);
// 将用户添加到model
model.addAttribute("user", user);
return "userInfo";
} else {
return "error";
}
} /**
* @Method: makeFileName
* @Description: 生成上传文件的文件名,文件的原始名称+"_"+uuid
* @param filename
* 文件的原始名称
* @param fileExtName
* @return 文件的原始名称+"_"+uuid
*/
private String makeFileName(String filenamePre, String fileExtName) { // 2.jpg
// 为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名
return filenamePre + "_" + UUID.randomUUID().toString() + "."+ fileExtName;
} /**
* 为防止一个目录下面出现太多文件,要使用hash算法打散存储
*
* @Method: makePath
* @param filename
* 文件名,要根据文件名生成存储目录
* @param savePath
* 文件存储路径
* @return 新的存储目录
*/
private Map<String, Object> makePath(String filename, String savePath) {
// 得到文件名的hashCode的值,得到的就是filename这个字符串对象在内存中的地址
int hashcode = filename.hashCode();
int dir1 = hashcode & 0xf; // 0--15
int dir2 = (hashcode & 0xf0) >> 4; // 0-15
// 构造新的保存目录
String dir = savePath + "\\" + dir1 + "\\" + dir2; // upload\2\3 upload\3\5
// File既可以代表文件也可以代表目录
File file = new File(dir);
// 如果目录不存在
if (!file.exists()) {
// 创建目录
file.mkdirs();
} Map<String, Object> map = new HashMap<String, Object>();
map.put("dir", dir);
map.put("dir1", dir1);
map.put("dir2", dir2);
return map;
} @RequestMapping(value = "/download")
public ResponseEntity<byte[]> download(HttpServletRequest request, @RequestParam("filename") String filename, Model model) throws Exception {
// 下载文件路径
String path = request.getServletContext().getRealPath("/WEB-INF/upload/");
File file = new File(path + File.separator + filename);
HttpHeaders headers = new HttpHeaders();
// 下载显示的文件名,解决中文名称乱码问题
String downloadFielName = new String(filename.getBytes("UTF-8"), "iso-8859-1");
// 通知浏览器以attachment(下载方式)打开图片
headers.setContentDispositionFormData("attachment", downloadFielName);
// application/octet-stream : 二进制流数据(最常见的文件下载)。
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(file), headers, HttpStatus.CREATED);
} }

文件上传的细节

上述的代码虽然可以成功将文件上传到服务器上面的指定目录当中,但是文件上传功能有许多需要注意的小细节问题,以下列出的几点需要特别注意的

  1. 为保证服务器安全,上传文件应该放在外界无法直接访问的目录下,比如放于WEB-INF目录下。
  2. 为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名。
  3. 为防止一个目录下面出现太多文件,要使用hash算法打散存储。
  4. 要限制上传文件的最大值。
  5. 要限制上传文件的类型,在收到上传文件名时,判断后缀名是否合法。

参考文章:

Spring MVC 实现文件的上传和下载