SpringMVC笔记--10文件上传下载MultipartResolver、ResponseEntity

Spring文件上传下载

文件上传主要依赖的是MultipartResolver;

文件下载主要依赖的是ResponseEntity

1 文件上传

1.1 文件上传的配置要求

  • form表单

form表单的enctype属性为multiple/form-data时,浏览器就会采用二进制流来处理表单数据,服务器就会对文件上传的请求进行解析处理。

  • Spring MVC文件上传实现类

Spring MVC通过MultipartResolver实现文件上传功能。

MultipartResolver是一个接口,

CommonsMultipartResolver是他的实现类,

实现类是基于commons-fileupload技术的,所以需要导入包commons-fileupload和commons-io。

  • 配置 MultipartResolver

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

<bean id="multipartResolver" 
      class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="defaultEncoding" value="UTF-8"></property>
    <property name="maxUploadSize" value="8242880"></property>
</bean>

属性:

  • id:必须为multipartResolver,因为实现类内部引用multipartResolver字符串获取实现类对象并完成文件解析的。

  • defaultEncoding:必须和用户 JSP 的 pageEncoding 属性一致,以便正确解析表单的内容

  • maxUploadSize:允许上传文件大小的最大值(字节)

  • maxInMemeory:缓存中的最大尺寸

  • resoleLazily:推迟文件解析,以便在Controller中捕获文件大小

而服务器端接收到的文件的类型是MultipartFile,在要上传的目标路径下New一个File,把传来的multipartFile对象充到这个File中即可,如file.transferTo(new File(文件路径/文件名));

1.2 文件上传示例

1) 拷贝jar包
commons-fileupload-1.2.1.jar
commons-io-2.0.jar
--------------------------
严重: Servlet /SpringMVC_06_FileUpload threw load() exception
java.lang.ClassNotFoundException: org.apache.commons.fileupload.FileItemFactory
2) 配置文件上传解析器springMVC.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- 配置扫描的包 -->
    <context:component-scan base-package="org.songwang"/>
    <!--配置注解驱动  -->
    <mvc:annotation-driven />
    <!-- 视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

    <!--配置拦截器-->
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="org.songwang.interceptor.MainInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

    
    <!-- 文件上传和拦截器配置没有关系-->
    <!--配置文件上传解析器  id 名称必须为multipartResolver-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!--设置请求的编码格式-->
        <property name="defaultEncoding" value="UTF-8"></property>
    </bean>
</beans>
3) 浏览器端上传页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.net.URLEncoder" %>
<html>
<head>
    <title>主页面-拦截器测试</title>
</head>
<body>
    <p>${USER_SESSION.userName}</p>
    <a href="${pageContext.request.contextPath}/loginOut">退出登陆</a>

    <%--表单方式完成文件的上传和下载--%>
    <form action="${pageContext.request.contextPath}/fileUpload"
          method="post"
          enctype="multipart/form-data"
          "checkUpload()">
        用户名:<input type="text" name="userName" id="userName"><br/>
        上传文件:<input type="file" name="uploadFiles" id="uploadFiles"><br/>
        <input type="submit" value="上传">
    </form>

    <!--
    <a
    href="${pageContext.request.contextPath}/download?filename=xiaobai_72165431-37c9-459c-b7a9-ce0d2077b04e_吉佑社 - 不爱又何必纠缠.mp3">
    下载</a>
    -->
    <a href="${pageContext.request.contextPath }/download?filename=<%=
                                   URLEncoder.encode("xiaobai_72165431-37c9-459c-b7a9-ce0d2077b04e_吉佑社 - 不爱又何必纠缠.mp3", "UTF-8")%>">
        下载
    </a>

    <script>
        function checkUpload(){
            var userName = document.getElementById("userName");
            var uploadFiles = document.getElementById("uploadFiles");

            if(userName ==''){
                alert("请输入用户名");
                return false;
            }
            if(uploadFiles=='' || uploadFiles.length == 0){
                alert("请选择上传的文件");
                return false;
            }
            return true;
        }
    </script>
</body>
</html>
4) 服务器端控制器方法
package org.songwang.controller;

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.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;
import java.util.UUID;

@Controller
public class FileUploadController {

	/**
	 * 文件上传-------------------------------------
	 */
	@PostMapping("/fileUpload")
	public String fileUpload(@RequestParam("userName") String userName,
	                         @RequestParam("uploadFiles") List<MultipartFile> uploadFiles,
	                         HttpServletRequest request) {
		// 判断文件是否存在
		if (!uploadFiles.isEmpty() && uploadFiles.size() > 0) {
			// 循环输出上传文件
			for (MultipartFile file : uploadFiles){
				// 获取原始的文件名称
				String originalFilename = file.getOriginalFilename();
				// 设置上传文件的保存目录
				String uploadPath = 
                    request.getServletContext().getRealPath("/upload/");
				File filePath = new File(uploadPath);//这里创建的是目录文件夹
				System.out.println(uploadPath);

				if(!filePath.exists()){
					// 如果目录不存在就创建
					filePath.mkdir();
				}
				// 使用uuid重新命名上传文件的文件名称(上传人_uuid_文件名)
				String newFileName = userName + "_"+UUID.randomUUID()+"_"+originalFilename;

				try {
					// 使用MultipartFile接口的方法完成文件上传到指定位置
                    //新建文件并将流传到文件
					file.transferTo(new File(filePath+"\\"+newFileName));
					System.out.println(filePath+"\\"+newFileName);
                    // InputStream inputStream = multipartFile.getInputStream();// 输入流
				} catch (IOException e) {
					e.printStackTrace();
					return "error";
				}
			}
			return "success";

		}
		return "error";
	}

    // 文件下载的部分可以先看完后面的再返回来看-------------------------------------
    // /download?filename=<%=URLEncoder.encode("xiaobai_72107b04e_比噶比噶.mp3", "UTF-8")%>
	@RequestMapping("/download")
	public ResponseEntity<byte[]> fileDownload(//返回类型为ResponseEntity<byte[]>
        HttpServletRequest request,                       
        String filename
    ) throws Exception{
	    // 指定要下载的文件所在路径
	    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);
        // res.setHeader("Content-Disposition","attachment;filename="+fileName);
	    // 定义以流的形式下载返回文件数据  //把二进制流放入到响应体中.
	    headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
	    // 使用Sring MVC框架的ResponseEntity对象封装返回下载数据
	   return new ResponseEntity<byte[]>(
           FileUtils.readFileToByteArray(file),//body//返回字节流
           headers,//头部
           HttpStatus.OK);//状态
        
        /*
        还有其他方式:
        BodyBuilder builder = (BodyBuilder) ResponseEntity.ok();
        builder.allow(HttpMethod.GET);
        builder.contentType(MediaType.TEXT_HTML);
        builder.eTag("W/MyeTag\"");
        return builder.body("Hello World!");
        ----------------------------------------
        ResponseEntity.status(HttpStatus.SC_CREATED).body("Created Success");
        ------------------------
        ResponseEntity.created(URL).
        header("MyResponseHeader","MyValue").body("Hello World");
 }
----------------------------

        */
	}
	/**
	 * 根据浏览器的不同进行编码设置,返回编码后的文件名
	 */
	public String getFilename(HttpServletRequest request,
	                          String filename) throws Exception {
		// IE不同版本User-Agent中出现的关键词 //这些都是IE
		String[] IEBrowserKeyWords = {"MSIE", "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编码显示 //以UTF-8方式编码为字节,然后再把字节解码为8859字符
		return new String(filename.getBytes("UTF-8"), "ISO-8859-1");
        // "中".getBytes("UTF-8");
	}
}

2 文件下载

下载文件的实现方式:

流程:请求给服务器发送的是URL,服务器根据这个URL把文件转成字节,发送给浏览器,浏览器就可以下载了。

服务器如何给浏览器发送文件字节流:
主要是通过ResponseEntity<T>,所以我们需要给他设定一个类型,常见的有ResponseEntity<String>用于返回字符串了;ResponseEntity<byte[]>用于返回文件。详见如下

ResponseEntity介绍:

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/ResponseEntity.html

ResponseEntity
构造函数:
    ResponseEntity(HttpStatus status)
    ResponseEntity(MultiValueMap<String,String> headers, HttpStatus status)
    ResponseEntity(T body, HttpStatus status)
	ResponseEntity(T body, MultiValueMap<String,String> headers, HttpStatus status)
方法:
    static ResponseEntity.BodyBuilder	accepted() //静态方法,可以通过ResponseEntity.accepted()调用,调用结果为创建一个具有ACCEPTED的builder
    static ResponseEntity.BodyBuilder	badRequest() 静态方法
    static ResponseEntity.BodyBuilder	created(URI location)
    static ResponseEntity.BodyBuilder	ok() //返回一个状态码为ok:200的builder
---------------需要插入BodyBuilder介绍一下。---------------

----------结合两者就可以使用了-------------------------------------------------

我们最终要看懂的代码如下,是要实现文件的下载,其中涉及的一些别的知识可以跳过往下看。

//在Spring MVC中。
@RequestMapping("/handle")
public ResponseEntity<String> handle() {
    URI location = ...;//要求下载的文件的url
    HttpHeaders responseHeaders = new HttpHeaders();//响应http头部
    responseHeaders.setLocation(location);
    responseHeaders.set("MyResponseHeader", "MyValue");
    return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);
}
也可以
ResponseEntity.created(location).header("MyResponseHeader", "MyValue").body("Hello World");
ResponseEntity.BodyBuilder介绍:

文档:https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/ResponseEntity.BodyBuilder.html

构造:
    通过ResponseEntity的静态方法调用,如ResponseEntity.ok()
方法:
    <T> ResponseEntity<T>	body(T body)//builder调用这个可以返回一个带有指定内容的http响应,如ResponseEntity.ok().body("123")
    ResponseEntity.BodyBuilder	contentLength(long contentLength)
    ResponseEntity.BodyBuilder	contentType(MediaType contentType)
    还有一些继承的方法:
    
HttpHeaders()介绍:

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/HttpHeaders.html

构造器:
    HttpHeaders() 构建一个空的HttpHeaders实例
    HttpHeaders(MultiValueMap<String,String> headers) 构建已经存在的Map构建一个HttpHeaders实例,这个Map里的key-value对应的是http响应头部的各个参数
    
方法:(简单举几例)
    getAccept() ;
    setAccept(List<MediaType> acceptableMediaTypes);
    setAcceptCharset(List<Charset> acceptableCharsets);
属性:
    ACCEPT :The HTTP Accept header field name.
    ACCEPT_CHARSET :The HTTP Accept-Charset header field name.
    ACCEPT_ENCODING:The HTTP Accept-Encoding header field name.
    ACCEPT_LANGUAGE:The HTTP Accept-Language header field name.

MediaType介绍:

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/MediaType.html


HttpStatus介绍:

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/HttpStatus.html

ACCEPTED :代表202
BAD_REQUEST 代表400
OK 200
NOT_FOUND 404 

这时候我们就能轻易看懂刚才的下载代码了

//在Spring MVC中。
@RequestMapping("/handle")
public ResponseEntity<String> handle() {
    URI location = ...;//要求下载的文件的url
    HttpHeaders responseHeaders = new HttpHeaders();//响应http头部,一会要传入ResponseEntity
    responseHeaders.setLocation(location);//继续设置头部的一些信息,随意设置,反正之后会传入ResponseEntity
    responseHeaders.set("MyResponseHeader", "MyValue");
    return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);//构造ResponseEntity,构造方法一次传入body、响应头、响应码
}
也可以
ResponseEntity.created(location).header("MyResponseHeader", "MyValue").body("Hello World");//解释:依次  调用created静态方法产生了builder,然后通过builder调用header方法产生的还是这个builder,builder调用body方法产生了一个ResponseEntity对象,

1.ResponseEntity的优先级高于@ResponseBody。在不是ResponseEntity的情况下才去检查有没有@ResponseBody注解。如果响应类型是ResponseEntity可以不写@ResponseBody注解,写了也没有关系。

2.ResponseEntity中的body和build什么时候使用?
看返回类型的泛型,build()的泛型是void,如果返回的是某一个对象而不是空的话就选择用body()。主要就是看T的类型,其功能都是一样的。

3 附:

        <form action="testUpload" method="post" enctype="multipart/form-data">
            文件: <input multiple="multiple" name="file"/><br/>
            描述: <input type="text" name="desc"/><br/>
            <input type="submit" value="提交"/>
        </form>
multiple属性是HTML5中新属性,可以实现多文件上传

ResponseEntity :标识整个http相应:状态码、头部信息、响应体内容(spring)

@ResponseBody:加在请求处理方法上,能够处理方法结果值作为http响应体(springmvc)

@ResponseStatus:加在方法上、返回自定义http状态码(spring)

考虑在SpringMVC.XML中设置静态资源访问权限
	<mvc:annotation-driven></mvc:annotation-driven>
	<!-- 静态资源 -->
	<mvc:resources location="/js/" mapping="/js/**"></mvc:resources>
	<mvc:resources location="/css/" mapping="/css/**"></mvc:resources>
	<mvc:resources location="/images/" mapping="/images/**"></mvc:resources>
	<mvc:resources location="/files/" mapping="/files/**"></mvc:resources>

文件下载的其他方式:

原理:

  • 先设置一下响应的头response.setHeader("Content-Disposition","attachment;filename="+fileName);
  • 通过response.getOutputStream();是创建输出流os,
  • 通过FileUtils.readFileToByteArray(file);把文件转成字节,
  • 然后把File字节注入到输出流中os.write(bytes);
    @RequestMapping("download")
    public void download(String fileName,HttpServletResponse res,
                         HttpServletRequest req) throws IOException{
        //设置响应流中文件进行下载// 通知浏览器以下载的方式打开文件
        res.setHeader("Content-Disposition","attachment;filename="+fileName);
        //把二进制流放入到响应体中.
        ServletOutputStream os = res.getOutputStream();
        String path = req.getServletContext().getRealPath("files");// 获取完整路径
        System.out.println(path);
        File file = new File(path, fileName);
        byte[] bytes = FileUtils.readFileToByteArray(file);
        os.write(bytes);
        os.flush();
        os.close();
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值