一、文件上传⬆
1.1 概述
Spring MVC中实现文件上传需要导入文件上传的jar包——commons-fileupload.jar,然后在springmvc.xml中配置文件上传解析器(或者在Spring MVC配置类中配置),然后创建对应控制器方法,对上传的文件进行解析,解析需要用到MultipartFile
类,该类作用于控制器方法的形参上,对应表单中文件上传的表单项
🔺回顾JavaWeb实现文件上传:在JavaWeb学习的文件上传功能中,需要先导入文件上传相关的jar包(commons-fileUpload.jar),然后使用jar包中的
ServletFileUpload
类判断当前提交的表单是否是多段格式,然后再调用parseRequest()
方法来解析上传数据并获取到表单项集合,最后遍历集合分别对不同类型的表单项做响应处理
1.2 实现文件上传的基本步骤
-
导入文件上传jar包
<!-- 文件上传jar包 --> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency>
-
配置文件上传解析器:对于上传到服务器的文件信息,Spring MVC不能直接获取,需要使用文件上传解析器来获取,而使用解析器需要先对其进行配置,有两种配置方式
① 在springmvc.xml中配置
<!-- 配置文件上传解析器,id值必须设置为multipartResolver --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 对文件上传解析器的某些属性赋值(可以不操作) --> <!-- 设置编码方式 --> <property name="defaultEncoding" value="utf-8"/> <!-- 设置上传文件大小上限,单位为字节(10485760=10M)--> <property name="maxUploadSize" value="10485760"/> <property name="maxInMemorySize" value="40960"/> </bean>
② 在Spring MVC配置类WebConfig中配置(完全注解开发)
/** * 配置文件上传解析器,解析器的bean-id(@Bean中的value属性值)必须设置为multipartResolver */ @Bean("multipartResolver") public CommonsMultipartResolver getMultipartResolver() { CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(); // 设置编码方法 multipartResolver.setDefaultEncoding("utf-8"); return multipartResolver; }
-
创建对应的控制器方法,方法的具体实现如下
① 添加
MultipartFile
类型的形参uploadFile
以及session
形参❓关于
MultipartFile
对象- 在配置了文件解析器之后,当提交表单时,Spring MVC会将此时上传的文件信息封装在
MultipartFile
对象中,在控制器方法中就能通过该对象来获取文件的信息并将文件写到指定位置 MultipartFile
对象作用于控制器方法形参,对应的是表单中上传文件的表单项(<input type="file">
标签),相当于对应上传的文件请求参数,而通过控制器方法形参获取请求参数时,形参名需要与请求参数名保持一致,如果不一致,则需添加@RequestParm("请求参数名")
注解来标识相应形参
② 通过
MultipartFile
对象获取上传的文件名(带后缀)// 1. 先获取上传的文件名 String filename = uploadFile.getOriginalFilename();
③ 先通过截取字符串的方式获取文件的后缀名,再通过
UUID.randomUUID()
随机生成一个字符串,将该字符串与后缀名拼接后替换原来的文件名,防止文件名冲突(也可以使用时间戳)// 2.1 获取文件的后缀名(文件名不为null和空情况下) String suffixName = ""; if (filename != null && ! "".equals(filename)) { // 截取字符串,获取字符串后面的后缀名 suffixName = filename.substring(filename.lastIndexOf(".")); } // 2.2 通过UUID随机生成一个字符串,与后缀名拼接替换原来的文件名,防止文件名冲突 filename = UUID.randomUUID().toString().replace("-", "") + suffixName;
④ 通过
session
形参对象获取servletContext
对象,然后通过servletContext
对象调用getRealPath("/photo")
方法获取工程下的photo目录路径dirPath
// 3. 通过session获取servletContext对象,并获取工程下的photo目录的路径 String dirPath = session.getServletContext().getRealPath("/photo");
❓关于
getRealPath()
获取的路径:servletContext
的getRealPath()
方法获取的是当前工程下的文件路径或目录路径,当前工程下是指工程的输出目录,而部署到tomcat服务器的Web工程输出目录默认为target目录下的名字与上下文路径(Application context)一样的目录,在idea的Project Structure中可以修改工程的输出目录。我当前Web工程名为SpringMVCLearn,上下文路径也是SpringMVCLearn,所以我这里通过getRealPath("/photo")
获取的完整路径应该为【D:/ideaProject/practice_ssm/SpringMVCLearn/target/SpringMVCLearn/photo】
out目录🆚target目录(参考博文:intellj idea中target目录和out目录的区别)
-
out目录存放的是该项目下所有Module(模块),即整个工程的编译结果(打包文件),在idea的Project Structure的Project中可以查看和修改
-
target目录存放的是单个Module的编译结果,在idea的Project Structure的Modules中可以查看和修改
-
在Modules中可以设置每个module的编译输出路径,如果指定了某个mudule的编译输出路径,一般输出到target目录,则不会再输出到out目录中
⑤ 创建
dirPath
对应的File
对象,再根据File
对象判断该目录是否存在,如果不存在就在对应位置创建photo目录// 4. 根据获取的photo目录路径创建File对象,再判断该目录是否存在,如果不存在就创建 File file = new File(dirPath); if (! file.exists()) { // 如果不存在就创建对应目录 file.mkdir(); }
⑥ 创建文件上传的最终路径
finalPath
,通过File.separator
设置文件之间的分隔符,然后再创建对应的File
对象// 5.1 设置文件上传的位置 String finalPath = dirPath + File.separator + filename; // 5.2 根据上传路径创建出对应的File对象 File finalPathFile = new File(finalPath);
PS:这里演示的是动态设置文件上传的路径,也可以设置固定的上传路径,设置固定的上传路径就无需进行④⑤⑥三步操作,直接创建上传路径对应的
File
对象即可⑦ 调用
MultipartFile
对象的transferTo()
方法,将文件上传的最终路径对应的File
对象传入即可// 6. 调用MultipartFile对象的transferTo()方法,将上传的文件写到对应的位置 uploadFile.transferTo(finalPathFile);
❓关于配置文件解析器的注意事项
- 文件解析器的bean-id值必须设置为
multipartResolver
(bean-id --><bean>
中的id值或@Bean
标签中的value
属性值) - 如果配置时不设置id值,或者id值设置为其他值,Spring MVC就找不到配置的文件解析器,也就无法为其创建对象,也不会为控制器方法中添加的
MultipartFile
类型的形参赋值,因此在控制器方法中使用的MultipartFile
对象就是个null,此时在前端页面点击提交表单,页面就会报500错误,因为在控制器方法中解析上传文件数据需要调用MultipartFile
对象的方法,而此时该对象为null,所以服务器会报空指针错误 - 如果采用第二种方式,即在配置类中配置文件上传解析器,则
@Bean
注解中可以不添加value
属性,但此时@Bean
所标识的方法名必须为multipartFile
,因为不设置value
属性值时,@Bean
注解会默认将方法名作为value
属性值 MultipartResolver
接口才是真正的文件上传解析器,但它是一个接口,一般不直接通过接口创建对象,而是使用其实现类来创建,因此配置文件上传解析器时,采用其实现类CommonsMultipartResolver
来创建文件上传解析器对象
- 在配置了文件解析器之后,当提交表单时,Spring MVC会将此时上传的文件信息封装在
1.3 完整代码
@RequestMapping("/fileUpload")
public String fileUpload(MultipartFile uploadFile, HttpSession session) throws IOException {
// 1. 先获取上传的文件名
String filename = uploadFile.getOriginalFilename();
System.out.println("上传文件名 --> " + filename);
// 2.1 获取文件的后缀名(文件名不为null和空情况下)
String suffixName = "";
if (filename != null && ! "".equals(filename)) {
// 截取字符串,获取字符串后面的后缀名
suffixName = filename.substring(filename.lastIndexOf("."));
}
// 2.2 通过UUID随机生成一个字符串,与后缀名拼接替换原来的文件名,防止文件名冲突
filename = UUID.randomUUID().toString().replace("-", "") + suffixName;
// 3. 通过session获取servletContext对象,并获取工程下的photo目录的路径
String dirPath = session.getServletContext().getRealPath("/photo");
// 4. 根据获取的photo目录路径创建File对象,再判断该目录是否存在,如果不存在就创建
File file = new File(dirPath);
if (! file.exists()) {
// 如果不存在就创建对应目录
file.mkdir();
}
// 5.1 设置文件上传的位置
String finalPath = dirPath + File.separator + filename;
// 设置固定的上传路径
// String finalPath =
// "D:\\ideaProject\\practice_ssm\\SpringMVCLearn\\src\\main\\webapp\\static\\image\\" +
// filename;
// 5.2 根据上传路径创建出对应的File对象
File finalPathFile = new File(finalPath);
// 6. 调用MultipartFile对象的transferTo()方法,将上传的文件写到对应的位置
uploadFile.transferTo(finalPathFile);
return "success";
}
二、文件下载⬇
2.1 概述
Spring MVC中可以直接在控制器方法中使用ResponseEntity
对象来设置响应报文信息,而且创建ResponseEntity
对象时直接传入文件字节数据(响应体信息)和响应头对象,然后将ResponseEntity
对象返回即可,无需创建输出流和手动设置响应体的MIME类型
🔺回顾JavaWeb实现文件下载:在JavaWeb实现的文件下载功能中,采用的是原生servletAPI,即在Servlet的
doPost()
方法中使用resp
对象获取并设置响应报文信息,在Spring MVC中当然也可以在控制器方法中添加resp
对象,然后通过resp
设置响应信息,用法与servletAPI中一样
2.2 实现文件下载的基本步骤
(在控制器方法中实现)
-
获取文件名请求参数:在控制器方法中添加文件名请求参数对应的形参
filename
-
获取
servletContext
对象,并通过该servletContext
对象获取要下载的文件资源并转化为输入流:在控制器方法中添加session
对象,通过session
对象获取servletContext
对象,然后通过servletContext
对象调用getResourceAsStream("文件路径")
方法将获取文件并转换为流// 2.1 获取servletContext对象 ServletContext servletContext = session.getServletContext(); // 2.2 通过servletContext对象获取文件资源并转化为输入流 InputStream fileIn = servletContext.getResourceAsStream("/static/image/code.png");
-
创建字节数组,并将文件输入流读取到字节数组中:字节数组的长度设置为
fileIn.available()
,表示输入流的全部字节数,通过输入流的read()
方法将流读取到数组中// 3. 创建字节数据,并将文件流读取字节数据中(fileIn.available()为输入流的全部字节数) byte[] bytes = new byte[fileIn.available()]; fileIn.read(bytes);
-
创建响应头对象,并通过响应头对象设置响应头信息:创建
MultiValueMap<String, String>
类型的响应头对象headers
,然后调用其add()
方法以键值对的形式设置响应头Content-Disposition
为attachment;filename=文件名
;除了文件名可变,其他形式固定,且必须设置// 4.1 创建请求头对象 MultiValueMap<String, String> headers = new HttpHeaders(); // 4.1 通过请求头设置请求头信息 headers.add("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "utf-8"));
-
创建响应状态码,并设置状态为OK
// 5. 创建响应状态码对象并设置响应状态为OK HttpStatus status = HttpStatus.OK;
-
创建
ResponseEntity
对象,泛型设置为<byte[]>
(控制器方法的返回值的泛型也一样),然后传入字节数组、响应头对象和响应状态码对象,最后关闭文件输入流的资源(别忘了!)// 6. 创建responseEntity对象,并传入文件字节数据(响应体信息)、响应头对象、响应状态码对象 ResponseEntity<byte[]> respEntity = new ResponseEntity<>(bytes, headers, status); // 7. 关闭资源 fileIn.close();
-
返回
ResponseEntity
对象
2.3 完整代码
@RequestMapping("/fileDownload")
public ResponseEntity<byte[]> fileDownload(String filename, HttpSession session) throws IOException {
// 1. 获取请求参数——文件名filename
System.out.println("文件名 -->" + filename);
// 2.1 获取servletContext对象
ServletContext servletContext = session.getServletContext();
// 2.2 通过servletContext对象获取文件资源并转化为输入流
InputStream fileIn = servletContext.getResourceAsStream("/static/image/code.png");
// 3. 创建字节数据,并将文件流读取字节数据中(fileIn.available()为输入流的全部字节数)
byte[] bytes = new byte[fileIn.available()];
fileIn.read(bytes);
// 4.1 创建请求头对象
MultiValueMap<String, String> headers = new HttpHeaders();
// 4.1 通过请求头设置请求头信息
headers.add("Content-Disposition", "attachment;filename=" +
URLEncoder.encode(filename, "utf-8"));
// 5. 创建响应状态码对象并设置响应状态为OK
HttpStatus status = HttpStatus.OK;
// 6. 创建responseEntity对象,并传入文件字节数据(响应体信息)、响应头对象、响应状态码对象
ResponseEntity<byte[]> respEntity = new ResponseEntity<>(bytes, headers, status);
// 7. 关闭资源
fileIn.close();
System.out.println("文件下载成功!");
return respEntity;
}