拦截器太强大了,吃透它(SpringMVC系列)

路人甲Java

共 15525字,需浏览 32分钟

 ·

2021-08-21 20:57

文末可领取最近刚整理的,后端必备的 200 本书籍。

1、本文内容

  • 回顾下 springmvc 处理请求的过程
  • 如何干预 springmvc 的处理流程?
  • 加入拦截器后 springmvc 的处理过程
  • 拦截器的用法(具体 2 个步骤)
  • 多个拦截器的执行顺序
  • 通过案例验证拦截器的执行顺序
  • 一起读源码
  • 领取后端必备的 200 本书籍

2、回顾下 springmvc 处理请求的过程

简化下过程,如下图,过程还是非常简单的

3、如何干预 springmvc 的处理流程?

比如我们的系统中,除了登录的方法,其他所有方法都需要先验证一下用户是否登录了,若未登录,让用户先跳转到登录页面,最笨的方法是在所有需要验证的方法内部都加上验证的代码,那么有没有更好的方法呢?

如下图,如果我们将验证登录的代码放在调用自定义controller的方法之前,是不是就特别爽了,就不用在所有代码中都添加验证代码了:

springmvc 确实为我们考虑到了这种需求,springmvc 在处理流程中为我们提供了 3 个扩展点可以对整个处理流程进行干预,这个就是 springmvc 中拦截器提供的功能,下面咱们来看一下拦截器怎么玩的。

4、拦截器(HandlerInterceptor)

springmvc 中使用org.springframework.web.servlet.HandlerInterceptor接口来表示拦截器,如下,提供了 3 个默认方法。

public interface HandlerInterceptor {

   default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
         throws Exception 
{

      return true;
   }

   default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
         @Nullable ModelAndView modelAndView)
 throws Exception 
{
   }

   default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
         @Nullable Exception ex)
 throws Exception 
{
   }

}

下面来解释下这 3 个方法。

preHandle 方法

在调用自定义的 controller 之前会调用这个方法,若返回 false,将跳过 controller 方法的调用,否则将进入到 controller 的方法中。

postHandle 方法

调用自定义 controller 中的方法之后会调用这个方法,此时还没有渲染视图,也就是还没有将结果输出到客户端

afterCompletion 方法

整个请求处理完毕之后,即结果以及输出到客户端之后,调用这个方法,此时可以做一些清理的工作,注意这个方法最后一个参数是 Exception 类型的,说明这个方法不管整个过程是否有异常,这个方法都会被调用。

其他说明

  • 3 个方法中的 hander 参数表示处理器,通常就是我们自定义的 controller

5、加入拦截器后 springmvc 的处理流程

加入 springmvc 之后处理流程如下图,注意黄色背景的几个对应拦截器的 3 个方法。

6、拦截器的用法(2 步骤)

step1:定义拦截器

自定义一个类,需要实现org.springframework.web.servlet.HandlerInterceptor接口,如下,然后实现具体的方法

public class HandlerInterceptor1 implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println(this.getClass().getSimpleName() + ".preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println(this.getClass().getSimpleName() + ".postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println(this.getClass().getSimpleName() + ".afterCompletion");
    }
}

step2:将自定义的拦截器添加到 springmvc 配置文件中

配置如下,需要将自定义的拦截器添加到 springmvc 配置文件中

  • 可以同时配置多个拦截器,每个拦截器通过<mvc:interceptor>标签来定义,多个拦截器之间可以指定顺序,顺序和<mvc:interceptor>定义的顺序一致
  • 每个拦截器需要指定实现类、拦截的 url、排除的 url
<!-- interceptors用来定义拦截器,其内部可以定义多个拦截器 -->
<mvc:interceptors>
    <!-- mvc:interceptor 标签用来定义一个拦截器 -->
    <mvc:interceptor>
        <!-- 用来指定拦截器匹配的url,比如/user/**会拦截所有以/user开头的url -->
        <mvc:mapping path="/user/**"/>
        <!-- 用来指定拦截器排除的url,即这些url不会被拦截器拦截 -->
        <mvc:exclude-mapping path="/user/login"/>
        <!-- 用来指定拦截器 -->
        <bean class="com.javacode2018.springmvc.chat09.intercetor.HandlerInterceptor1"/>
    </mvc:interceptor>
    <!-- 其他拦截器配置信息 -->
    <mvc:interceptor>
        .....
    </mvc:interceptor>
</mvc:interceptors>

7、多个拦截器时如何执行?

当请求的 url 匹配到多个拦截器的时候,执行顺序如下图

  • preHandler 方法是顺序执行的,即和定义的顺序是一致的
  • 而拦截器中的其他 2 个方法 postHandler、afterCompletion 是逆序执行的,和 pewHandler 的顺序相反

8、案例验证拦截器的执行顺序

下面通过案例结合 3 个场景来看一下拦截器的执行顺序,加深大家的理解。

准备代码

UserController

package com.javacode2018.springmvc.chat09.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/login")
    public String login() {
        return "login view";
    }

    @RequestMapping("/add")
    public String add() {
        return "add view";
    }

    @RequestMapping("/del")
    public String modify() {
        return "modify view";
    }

    @RequestMapping("/list")
    public String list() {
        return "list view";
    }
}

创建 2 个拦截器

拦截器 1:HandlerInterceptor1
package com.javacode2018.springmvc.chat09.intercetor;


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

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

public class HandlerInterceptor1 implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println(this.getClass().getSimpleName() + ".preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println(this.getClass().getSimpleName() + ".postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println(this.getClass().getSimpleName() + ".afterCompletion");
    }
}
拦截器 2:HandlerInterceptor2
package com.javacode2018.springmvc.chat09.intercetor;


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

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

public class HandlerInterceptor2 implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println(this.getClass().getSimpleName() + ".preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println(this.getClass().getSimpleName() + ".postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println(this.getClass().getSimpleName() + ".afterCompletion");
    }
}

配置文件中配置拦截器

下面将 2 个拦截器加入 springmvc 的配置文件中,都会拦截/user 开头的所有请求,/user/login 被排除在外

<!-- interceptors用来定义拦截器,其内部可以定义多个拦截器 -->
<mvc:interceptors>
    <!-- mvc:interceptor 标签用来定义一个拦截器 -->
    <mvc:interceptor>
        <!-- 用来指定拦截器匹配的url -->
        <mvc:mapping path="/user/**"/>
        <!-- 用来指定拦截器排除的url,即这些url不会被拦截器拦截 -->
        <mvc:exclude-mapping path="/user/login"/>
        <!-- 用来指定拦截器 -->
        <bean class="com.javacode2018.springmvc.chat09.intercetor.HandlerInterceptor1"/>
    </mvc:interceptor>
    <!-- mvc:interceptor 标签用来定义一个拦截器 -->
    <mvc:interceptor>
        <!-- 用来指定拦截器匹配的url -->
        <mvc:mapping path="/user/**"/>
        <!-- 用来指定拦截器排除的url,即这些url不会被拦截器拦截 -->
        <mvc:exclude-mapping path="/user/login"/>
        <!-- 用来指定拦截器 -->
        <bean class="com.javacode2018.springmvc.chat09.intercetor.HandlerInterceptor2"/>
    </mvc:interceptor>
</mvc:interceptors>

场景 1

按照下列表格,调整下 2 个拦截器的 preHandle 方法返回值

拦截器preHandle 方法返回值
HandlerInterceptor1true
HandlerInterceptor2true

访问http://localhost:8080/chat09/user/add,输出

HandlerInterceptor1.preHandle
HandlerInterceptor2.preHandle
HandlerInterceptor2.postHandle
HandlerInterceptor1.postHandle
HandlerInterceptor2.afterCompletion
HandlerInterceptor1.afterCompletion

场景 2

按照下列表格,调整下 2 个拦截器的 preHandle 方法返回值

拦截器preHandle 方法返回值
HandlerInterceptor1false
HandlerInterceptor2true

访问http://localhost:8080/chat09/user/add,输出

HandlerInterceptor1.preHandle

场景 3

按照下列表格,调整下 2 个拦截器的 preHandle 方法返回值

拦截器preHandle 方法返回值
HandlerInterceptor1true
HandlerInterceptor2false

访问http://localhost:8080/chat09/user/add,输出

HandlerInterceptor1.preHandle
HandlerInterceptor2.preHandle
HandlerInterceptor1.afterCompletion

9、源码解析

拦截器的执行过程主要位于下面这段代码中

代码位置:org.springframework.web.servlet.DispatcherServlet#doDispatch

如下代码,咱们将关键代码提取出来,大家注意看注释,解释了每个方法内部干的事情,具体每个方法的内部,咱们就不进去了,很简单,有兴趣的可以自己去看,这里给大家提点建议:看源码的时候,先站在高的层次上面看代码,了解大的功能及流程之后,再去细看某个功能点,要避免上来就陷入细节中,容易迷失方向。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    try {
        Exception dispatchException = null;
        try {
            //①、根据请求找到处理器
            mappedHandler = getHandler(processedRequest);
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            //②、内部会调用拦截器的preHandler方法
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            //③、调用实际的处理器(即这里面会调用咱们controller中的方法)
            ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            //④、调用拦截器的postHandle方法
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        } catch (Exception ex) {
            dispatchException = ex;
        }

        //⑤、渲染视图 & 调用拦截器的afterCompletion
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    } catch (Exception ex) {
        //⑥:异常情况,调用拦截器的afterCompletion
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
}

10、案例代码

git地址:https://gitee.com/javacode2018/springmvc-series

11、SpringMVC 系列

  1. SpringMVC 系列第 1 篇:helloword
  2. SpringMVC 系列第 2 篇:@Controller、@RequestMapping
  3. SpringMVC 系列第 3 篇:异常高效的一款接口测试利器
  4. SpringMVC 系列第 4 篇:controller 常见的接收参数的方式
  5. SpringMVC 系列第 5 篇:@RequestBody 大解密,说点你不知道的
  6. SpringMVC 系列第 6 篇:上传文件的 4 种方式,你都会么?
  7. SpringMVC 系列第 7 篇:SpringMVC 返回视图常见的 5 种方式,你会几种?
  8. SpringMVC 系列第 8 篇:返回 json & 通用返回值设计
  9. SpringMVC 系列第 9 篇:SpringMVC 返回 null 是什么意思?
  10. SpringMVC 系列第 10 篇:异步处理太强大了,系统性能大幅提升
  11. SpringMVC 系列第 11 篇:集成静态资源

12、更多好文章

  1. Spring 高手系列(共 56 篇)
  2. Java 高并发系列(共 34 篇)
  3. MySql 高手系列(共 27 篇)
  4. Maven 高手系列(共 10 篇)
  5. Mybatis 系列(共 12 篇)
  6. 聊聊 db 和缓存一致性常见的实现方式
  7. 接口幂等性这么重要,它是什么?怎么实现?
  8. 泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!

13、领取后端必备的 200 本书

这里帮大家整理 200 本后端必备的书籍,扫描下面二维码即可看到书籍列表,直接领取电子版,也可以点击【阅读原文】领取

浏览 11
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报