SpringMVC4-文件上传下载-拦截器

1 文件下载

通过ResponseEntity封装以下三组信息返回下载

  1. 数据缓存数组:bytes数组
  2. 响应头信息:HttpHeader,内容填充格式固定
  3. 响应码信息:HttpStatus,例如404
/**
 * @author : Lcywings
 * @date : 2021/6/29 8:42
 * @acl : true
 * @description : 图片下载
 * 下载原理:将服务器端的文件以流的形式写到客户端,通过浏览器保存实现下载
 */
@RequestMapping("/downloadPic")
public ResponseEntity<byte[]> downloadPic(HttpSession session) throws IOException {
    // 字节缓冲数组
    byte[] bytes = null;

    // 读取服务器上的图片,借助session获取ServletContext对象,有一个将文件读成流的方法
    InputStream inputStream = session.getServletContext().getResourceAsStream("pic/girl.jpg");

    // 定义缓存数组大小,目标文件多大,创建多大的缓存
    bytes = new byte[inputStream.available()];

    // 将目标图片文件,全部读取到缓存数组中
    inputStream.read(bytes);

    // 使用ResponseEntity封装相应数据
    // 封装的内容: 1 发送给客户端的响应数据 2 响应头信息 3 响应码状态
    // 响应头信息
    HttpHeaders headers = new HttpHeaders();
    // 下面是固定写法,不能乱写,attachment代表附件,filename提示下载的文件名,可以随机或者自定义
    headers.add("Content-Disposition", "attachment;filename=girl.jpg");

    // 设置响应码
    HttpStatus status = HttpStatus.OK;

    // 封装信息
    ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, status);

    //返回封装信息
    return responseEntity;

}

2 文件上传

文件上传的三板斧

  1. 获取上传文件名,及其需要创建文件存储目录
  2. 根据上面俩条创建目标文件
  3. 上传文件
 /**
     * @author : Lcywings
     * @date : 2021/6/29 9:41
     * @acl : true
     * @description : 文件上传
     */
    @RequestMapping(value = "uploadPic", method = RequestMethod.POST)
    public String uploadPic(@RequestParam("uploadFile") MultipartFile multipartFile,
                            HttpSession session, Map<String, Object> map) throws IOException {

        // 获取到上传文件的名称
        String fileName = multipartFile.getOriginalFilename();

        // 获取服务器上传文件的真实路径(还可以指定服务器的固定目录)
        String realPath = session.getServletContext().getRealPath("upload");
        System.out.println(realPath);

        // 创建文件对象
        File file = new File(realPath);
        // 判断目录是否存在,如果不存在,就创建目标目录,如果存在就直接使用
        if (!file.exists()) {
            if (file.mkdirs()) {
                System.out.println("目标目录不存在,创建成功!");
            } else {
                System.out.println("目标目录不存在,创建失败!");
            }
        }

        // 创建一个目标文件对象
        File targeFile = new File(realPath + "/" + fileName);

        // 基础写法
        // 获取源文件的输入流对象
//        InputStream inputStream = multipartFile.getInputStream();
//
//        //创建输出流对象
//        FileOutputStream fos = new FileOutputStream(targeFile);
//
//        // 循环读取
//        int hasRead;
//        while ((hasRead = inputStream.read()) != -1) {
//            fos.write(hasRead);
//        }
//        fos.flush();
//        inputStream.close();
//        fos.close();

        // 高级用法
        multipartFile.transferTo(targeFile);

        // 将文件名返回页面,方便显示上传的头像
        map.put("fileName", fileName);

        // 不走视图解析
        return "forward:load.jsp";
    }

主要作用是为了使用即插即用的 MultipartResolver,搞到对象

<!-- 文件上传组件 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- 保证字符集编码必须跟Jsp页面使用的编码一致 -->
    <property name="defaultEncoding" value="utf-8"/>
    <!-- 上传文件的大小限制 -->
    <property name="maxInMemorySize" value="10485760"/>
</bean>

拦截器

拦截器过滤器的区别:

  • 过滤器:在请求到达servlet之前过滤请求,比如:字符集编码过滤器,免登陆过滤器,rest风格过滤器,安全校验过滤器等
  • 拦截器:在请求到达servlet中执行,拦截非法请求,比如:敏感字拦截,非法请求拦截等

拦截器生效需要的配置:

<!-- 让拦截器生效,必须增加配置,加入拦截器 -->
<mvc:interceptors>
    <ref bean="myInterceptor"/>
</mvc:interceptors>

自定义拦截器:

  1. 实现 HandlerInterceptor 接口

  2. 继承 HandlerInterceptorAdapter 适配器类

需要重写三个方法:

  1. preHandle()
  2. postHandle()
  3. afterCompletion()
package com.kgc.springmvc.interceptor;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Created on 2021-06-29.
 * <p>
 * Author: zhukang
 * <p>
 * Description: 自定义拦截器
 *
 * 拦截器和过滤器的区别:
 *  过滤器:在请求到达servlet之前过滤请求,比如:字符集编码过滤器,免登陆过滤器,rest风格过滤器,安全校验过滤器等
 *  拦截器:在请求到达servlet中执行,拦截非法请求,比如:敏感字拦截,非法请求拦截等
 *  总结一点:过滤器早于拦截器执行
 *
 * 拦截器的实现方式:
 * 1)实现 HandlerInterceptor 接口 2)继承 HandlerInterceptorAdapter 适配器类
 */
@Component
public class MyInterceptor implements HandlerInterceptor {

    /**
     * 执行时机:在调用目标请求处理器的请求处理方法之前执行,在DispatcherServlet的962行
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("------ MyInterceptor preHandle ... ------");
        // true:代表请求允许访问,不需要拦截,将请求交给后续的拦截器或者目标请求处理器中的处理方法
        // false:代表请求不允许访问,需要拦截,请求就不会交给后续的拦截器或者目标请求处理器中的处理方法
        return true;
    }

    /**
     * 执行时机:在目标请求处理器的请求处理方法执行后,在模型属性渲染之前调用,在DispatcherServlet的974行
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("------ MyInterceptor postHandle ... ------");
    }

    /**
     * 执行时机:
     *  正常:没有发生异常,在视图处理(渲染模型数据,转发请求等)后,调用,在1059行,从最后一个拦截器开始依次往前执行此方法
     *  产生异常,调用triggerAfterCompletion方法,在987和990行
     *
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("------ MyInterceptor afterCompletion ... ------");
    }
}

多个拦截器:

→ | → | → | → | → |
​ ↓这个拦截为false,则直接从上一个拦截器看是执行afterCompletion方法,
← | ← | ← postHandle则不执行

package com.kgc.springmvc.interceptor;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Created on 2021-06-29.
 * <p>
 * Author: zhukang
 * <p>
 * Description: 自定义拦截器2
 * 多个拦截器,执行次序:
 *  preHandle:正向执行的,跟核心配置文件中,配置的拦截器的顺序有关
 *  postHandle:逆向执行的,由于都是先走完所有的拦截器的preHandle方法,如果有一个拦截器的preHandle方法返回false,所有的拦截器的postHandle方法就不会执行
 *  afterCompletion:逆向执行的,从当前拦截器之前的那个拦截器逆序依次执行
 */
@Component
public class MyInterceptor2 implements HandlerInterceptor {

    /**
     * 执行时机:在调用目标请求处理器的请求处理方法之前执行,在DispatcherServlet的962行
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("------ MyInterceptor2 preHandle ... ------");
        // true:代表请求允许访问,不需要拦截,将请求交给后续的拦截器或者目标请求处理器中的处理方法
        // false:代表请求不允许访问,需要拦截,请求就不会交给后续的拦截器或者目标请求处理器中的处理方法
        return false;
    }

    /**
     * 执行时机:在目标请求处理器的请求处理方法执行后,在模型属性渲染之前调用,在DispatcherServlet的974行
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("------ MyInterceptor2 postHandle ... ------");
    }

    /**
     * 执行时机:
     *  正常:没有发生异常,在视图处理(渲染模型数据,转发请求等)后,调用,在1059行,从最后一个拦截器开始依次往前执行此方法
     *  产生异常,调用triggerAfterCompletion方法,在987和990行
     *
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("------ MyInterceptor2 afterCompletion ... ------");
    }
}

释放静态资源的问题解决

<!--  springmvc默认是不会释放静态资源(js,css,image,html...),即被前端核心控制器拦截(找映射处理方法)
   针对静态资源要进行释放,增加一个配置<mvc:default-servlet-handler/>即可
   注意:静态资源释放会导致@RequestMapping注解失效,必须配置注解扫描使用
-->
<mvc:default-servlet-handler/>
<!--
   作用:
    1)配置了<mvc:view-controller/>
    2)配置了<mvc:default-servlet-handler/>
    3)处理json数据返回,默认装配的对象:RequestMappingHandlerAdapter,默认对HttpMessageConverter提供支持

   原理:
    比如随着spring版本升级,老的版本不够完善,在后续版本中需要提供新的组件来代替
        启动新的组件代替旧的组件,实现新的强大的功能
-->
<mvc:annotation-driven/>

SpringIOC容器 & SpringMVCIOC容器

SpringMVC 的 IOC 容器中的 bean 可以来引用 Spring IOC 容器中的 bean. 返回来呢 ? 反之则不行. Spring IOC 容器中的 bean 却不能来引用 SpringMVC IOC 容 器中的 bean 。

  1. 在 Spring MVC 配置文件中引用业务层的 Bean
  2. 多个 Spring IOC 容器之间可以设置为父子关系,以实现良好的解耦。
  3. Spring MVC WEB 层容器可作为 “业务层” Spring 容器的子容器: 即 WEB 层容器可以引用业务层容器的 Bean,而业务层容器却访问不到 WEB 层容 器的 Bean

Spring 工作流程描述

1) 用户向服务器发送请求,请求被 SpringMVC 前端控制器 DispatcherServlet 捕获;

2) DispatcherServlet 对请求 URL 进行解析,得到请求资源标识符(URI):
​ 判断请求 URI 对应的映射

​ 1 不存在:
​ - 再判断是否配置了 mvc:default-servlet-handler:
​ - 如果没配置,则控制台报映射查找不到,客户端展示 404 错误
​ - 如果有配置,则执行目标资源(一般为静态资源,如:JS,CSS,HTML)

​ 2 存在:
​ - 执行下面流程

3) 根据该 URI,调用 HandlerMapping 获得该 Handler 配置的所有相关的对象(包括 Handler 对象以及 Handler 对象对应的拦截器),最后以 HandlerExecutionChain 对象 的形式返回;

4) DispatcherServlet 根据获得的 Handler,选择一个合适的 HandlerAdapter。

5) 如果成功获得 HandlerAdapter 后,此时将开始执行拦截器的 preHandler(...)方法【正 向】

6) 提取 Request 中的模型数据,填充 Handler 入参,开始执行 Handler(Controller)方法, 处理请求。在填充 Handler 的入参过程中,根据你的配置,Spring 将帮你做一些额外 的工作:

​ - HttpMessageConveter:将请求消息(如 Json、xml 等数据)转换成一个对象,将对象转换为指定的响应信息
​ - 数据转换:对请求消息进行数据转换。如 String 转换成 Integer、Double 等
​ - 数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
​ - 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult 或 Error 中

7) Handler 执行完成后,向 DispatcherServlet 返回一个 ModelAndView 对象;

8) 此时将开始执行拦截器的 postHandle(...)方法【逆向】

9) 根据返回的 ModelAndView(此时会判断是否存在异常:如果存在异常,则执行 HandlerExceptionResolver 进行异常处理)选择一个适合的 ViewResolver(必须是已 经注册到Spring容器中的ViewResolver)返回给DispatcherServlet,根据Model和View, 来渲染视图

10) 在返回给客户端时需要执行拦截器的 AfterCompletion 方法【逆向】

11) 将渲染结果返回给客户端

Q.E.D.