Spring MVC 教程-拦截器怎么用?

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析


阶段4、深入jdk其余源码解析


阶段5、深入jvm源码解析

码哥源码部分

码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】

码哥讲源码【炸雷啦!炸雷啦!黄光头他终于跑路啦!】

码哥讲源码-【jvm课程前置知识及c/c++调试环境搭建】

​​​​​​码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】

码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】

码哥讲源码【你水不是你的错,但是你胡说八道就是你不对了!】

码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】

终结B站没人能讲清楚红黑树的历史,不服等你来踢馆!

打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】

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的处理流程

加入拦截器之后处理流程如下图,注意黄色背景的几个对应拦截器的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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值