SpringBoot拦截器以及源码详析

目录
  • 1、拦截器是什么
  • 2、自定义拦截器
    • 2.1 编写拦截器
    • 2.2 注册和配置拦截器
  • 3、拦截器原理
    • 3.1 找到可以处理请求的handler以及handler的所有拦截器
    • 3.2 执行拦截器的preHandle方法
    • 3.3 执行目标方法
    • 3.4 执行拦截器的postHandle方法
    • 3.5 执行拦截器的afterCompletion方法
    • 3.6 异常处理
  • 4、总结

1、拦截器是什么

java里的拦截器(Interceptor)是动态拦截Action调用的对象,它提供了一种机制可以使开发者在一个Action执行的前后执行一段代码,也可以在一个Action执行前阻止其执行,同时也提供了一种可以提取Action中可重用部分代码的方式。在AOP中,拦截器用于在某个方法或者字段被访问之前进行拦截,然后再之前或者之后加入某些操作。

上面的Action一般指的就是我们Controller层的接口。

2、自定义拦截器

一般自定义一个拦截器分为三步

(1)编写一个拦截器实现HandlerInterceptor接口。

(2)拦截器注册到容器中。

(3)配置拦截规则。

2.1 编写拦截器

我们新建一个SpringBoot项目,然后自定义一个拦截器LoginInterceptor,拦截未登录状态下的某些请求。JDK1.8开始,接口的方法加上default关键字可以有默认实现,所以实现一个接口只需要实现没有加该关键字的方法。

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

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

/**
 * 登录拦截器
 */
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    /**
     * 目标方法执行之前执行
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取请求路径
        String requestUrl = request.getRequestURI();
        log.info("请求的路径是: {}", requestUrl);

        String username = request.getParameter("username");
        if (username != null) {
            // 放行
            return true;
        }

        request.setAttribute("msg", "请先登录");
        // 携带msg跳转到登录页
        request.getRequestDispatcher("/").forward(request, response);
        return false;
    }

    /**
     * 目标方法完成以后执行
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle执行");
    }

    /**
     * 页面渲染以后执行
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion执行");
    }
}

2.2 注册和配置拦截器

在SpringBoot中,我们需要自定义配置的时候,只需要实现WebMvcConfigurer类重写对应的方法即可。这里我们需要配置拦截器,那么重写它的addInterceptors方法即可。

import com.codeliu.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

// 表示这是一个配置类
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")  // 拦截所有路径
                .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**");  // 不拦截这些路径
    }
}

注意如果我们配置了拦截所有的路径,那么一定要排除掉静态资源,不然图片样式都会被拦截。

通过上面几步,我们就实现了一个给系统加了一个拦截器。启动验证即可。

3、拦截器原理

我们通过打断点调试的方法,看看从浏览器请求开始到后端是如何进行处理的。在DispatcherServlet的doDispatch方法打上断点,这是请求的入口,浏览器发送请求后,由此方法进行转发和处理。

debug模式启动应用,访问任意接口,跟踪代码流程

3.1 找到可以处理请求的handler以及handler的所有拦截器

这里找到了HandlerExecutionChain以及拦截器链,里面有三个拦截器,我们自定义的LoginInterceptor和系统默认的两个拦截器。

3.2 执行拦截器的preHandle方法

在doDispatch方法中,有下面两行代码

// 执行拦截器的preHandle方法,如果返回为fasle,则直接return,不执行目标方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
}

// 反射执行目标方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

我们进入applyPreHandle方法,看看该方法的逻辑

/**
 * Apply preHandle methods of registered interceptors.
 * @return {@code true} if the execution chain should proceed with the
 * next interceptor or the handler itself. Else, DispatcherServlet assumes
 * that this interceptor has already dealt with the response itself.
 */
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 遍历拦截器
    for (int i = 0; i < this.interceptorList.size(); i++) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        // 执行当前拦截器的preHandle方法
        if (!interceptor.preHandle(request, response, this.handler)) {
            // 如果preHandle方法返回为false,则执行当前拦截器的afterCompletion方法
            triggerAfterCompletion(request, response, null);
            return false;
        }
        // 记录当前拦截器的下标
        this.interceptorIndex = i;
    }
    return true;
}

通过上面的代码, 我们知道如果当前拦截器的preHandle方法返回为true,则会继续执行下一拦截器的preHandle方法,否则执行拦截器的afterCompletion方法。

那么我们看看triggerAfterCompletion方法的逻辑。

/**
 * Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
 * Will just invoke afterCompletion for all interceptors whose preHandle invocation
 * has successfully completed and returned true.
 */
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
    // 反向遍历拦截器
    for (int i = this.interceptorIndex; i >= 0; i--) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        try {
            // 执行当前拦截器的afterCompletion方法
            interceptor.afterCompletion(request, response, this.handler, ex);
        }
        catch (Throwable ex2) {
            logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
        }
    }
}

通过上面的代码,我们知道对于拦截器的afterCompletion方法,是反向执行的。

3.3 执行目标方法

如果上面拦截器的所有preHandle方法返回都为true,那么在doDispatch方法内就不会直接return,而是继续往下执行目标方法。如果任何一个拦截器的preHandle方法返回为false,那么执行完拦截器(已经执行过preHandle方法的拦截器)的afterCompletion方法后,在doDispatch方法内会直接return,不会执行目标方法。

通过下面的代码执行目标方法

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

内部具体怎么执行的就不看了,看看执行完后的逻辑。

3.4 执行拦截器的postHandle方法

目标方法执行完后,代码往下走

mappedHandler.applyPostHandle(processedRequest, response, mv);

查看applyPostHandle的逻辑

/**
 * Apply postHandle methods of registered interceptors.
 */
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
    throws Exception {
	// 反向遍历
    for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        // 执行当前拦截器的postHandle方法
        interceptor.postHandle(request, response, this.handler, mv);
    }
}

倒序执行拦截器的postHandle方法

3.5 执行拦截器的afterCompletion方法

继续往下走

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

进入该方法,该方法会处理执行结果,渲染页面,该方法的最后,执行下面的代码

3.6 异常处理

如果在执行doDispatch方法过程中,抛出了异常,在catch模块,都会触发执行afterCompletion方法

4、总结

上面的过程,大概可以总结为以下几步:

(1)根据当前请求,找到可以处理请求的handler和handler的所有拦截器。

(2)顺序执行所有拦截器的preHandle方法

如果当前拦截器的preHandle方法返回为true,则执行下一拦截器的preHandle方法。如果当前拦截器返回为false,倒序执行所有已经执行了的拦截器的afterCompletion。

(3)如果任何一个拦截器返回false,执行返回,不执行目标方法。

(4)所有拦截器都返回true,执行目标方法。

(5)倒序执行所有拦截器的postHandle方法。

(6)前面的步骤有任何异常都会触发倒序执行afterCompletion方法。

(7)页面成功渲染后,也会倒序执行afterCompletion方法。

到此这篇关于SpringBoot拦截器以及源码的文章就介绍到这了,更多相关SpringBoot拦截器内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SpringBoot拦截器的使用小结

    总结一下SpringBoot下拦截器的使用,步骤很简单: 1.自定义自己的拦截类,拦截类需要继承HandlerInterceptor接口并实现这个接口的方法. @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { //方法调用前执行 return true;//返回

  • 详谈springboot过滤器和拦截器的实现及区别

    前言 springmvc中有两种很普遍的AOP实现: 1.过滤器(Filter) 2.拦截器(Interceptor) 本篇面对的是一些刚接触springboot的人群 所以主要讲解filter和interceptor的简单实现和它们之间到底有什么区别 (一些复杂的功能我会之后发出文章,请记得关注) Filter的简单实现 字面意思:过滤器就是过滤的作用,在web开发中过滤一些我们指定的url 那么它能帮我们过滤什么呢? 那功能可就多了: 比如过拦截掉我们不需要的接口请求 修改请求(reques

  • 详解SpringBoot AOP 拦截器(Aspect注解方式)

    常用用于实现拦截的有:Filter.HandlerInterceptor.MethodInterceptor 第一种Filter属于Servlet提供的,后两者是spring提供的,HandlerInterceptor属于Spring MVC项目提供的,用来拦截请求,在MethodInterceptor之前执行. 实现一个HandlerInterceptor可以实现接口HandlerInterceptor,也可以继承HandlerInterceptorAdapter类,两种方法一样.这个不在本文

  • SpringBoot快速设置拦截器并实现权限验证的方法

    一.概述 拦截器的使用场景越来越多,尤其是面向切片编程流行之后.那通常拦截器可以做什么呢? 之前我们在Agent介绍中,提到过统计函数的调用耗时.这个思路其实和AOP的环绕增强如出一辙. 那一般来说,场景如下: 函数增强:比如对一个函数进行参数检查,或者结果过滤等.甚至可以对函数就行权限认证. 性能监控:统计函数性能. 日志打点:比如在用户登录函数之前,打点统计PV等信息. 以及其他等等. 二.Spring的拦截器 无论是SpringMVC或者SpringBoot中,关于拦截器不得不提: org

  • springboot实现拦截器之验证登录示例

    整理文档,搜刮出一个springboot实现拦截器之验证登录示例,稍微整理精简一下做下分享. 添加jar包,这个jar包不是必须的,只是在拦截器里用到了,如果不用的话,完全可以不引入 <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.5</version> </dep

  • spring boot如何添加拦截器

    构建一个spring boot项目. 添加拦截器需要添加一个configuration @Configuration @ComponentScan(basePackageClasses = Application.class, useDefaultFilters = true) public class ServletContextConfig extends WebMvcConfigurationSupport { 为了方便扫描位置,我们可以写一个接口或者入口类Application放置于最外

  • SpringBoot拦截器实现登录拦截的方法示例

    源码 GitHub:https://github.com/291685399/springboot-learning/tree/master/springboot-interceptor01 SpringBoot拦截器可以做什么 可以对URL路径进行拦截,可以用于权限验证.解决乱码.操作日志记录.性能监控.异常处理等 SpringBoot拦截器实现登录拦截 pom.xml: <?xml version="1.0" encoding="UTF-8"?> &

  • SpringBoot拦截器以及源码详析

    目录 1.拦截器是什么 2.自定义拦截器 2.1 编写拦截器 2.2 注册和配置拦截器 3.拦截器原理 3.1 找到可以处理请求的handler以及handler的所有拦截器 3.2 执行拦截器的preHandle方法 3.3 执行目标方法 3.4 执行拦截器的postHandle方法 3.5 执行拦截器的afterCompletion方法 3.6 异常处理 4.总结 1.拦截器是什么 java里的拦截器(Interceptor)是动态拦截Action调用的对象,它提供了一种机制可以使开发者在一

  • 关于Redis网络模型的源码详析

    前言 Redis的网络模型是基于I/O多路复用程序来实现的.源码中包含四种多路复用函数库epoll.select.evport.kqueue.在程序编译时会根据系统自动选择这四种库其中之一.下面以epoll为例,来分析Redis的I/O模块的源码. epoll系统调用方法 Redis网络事件处理模块的代码都是围绕epoll那三个系统方法来写的.先把这三个方法弄清楚,后面就不难了. epfd = epoll_create(1024); 创建epoll实例 参数:表示该 epoll 实例最多可监听的

  • YOLOv5中SPP/SPPF结构源码详析(内含注释分析)

    目录 一.SPP的应用的背景 二.SPP结构分析 三.SPPF结构分析 四.YOLOv5中SPP/SPPF结构源码解析(内含注释分析) 总结 一.SPP的应用的背景 在卷积神经网络中我们经常看到固定输入的设计,但是如果我们输入的不能是固定尺寸的该怎么办呢? 通常来说,我们有以下几种方法: (1)对输入进行resize操作,让他们统统变成你设计的层的输入规格那样.但是这样过于暴力直接,可能会丢失很多信息或者多出很多不该有的信息(图片变形等),影响最终的结果. (2)替换网络中的全连接层,对最后的卷

  • Java并发编程学习之ThreadLocal源码详析

    前言 多线程的线程安全问题是微妙而且出乎意料的,因为在没有进行适当同步的情况下多线程中各个操作的顺序是不可预期的,多线程访问同一个共享变量特别容易出现并发问题,特别是多个线程需要对一个共享变量进行写入时候,为了保证线程安全, 一般需要使用者在访问共享变量的时候进行适当的同步,如下图所示: 可以看到同步的措施一般是加锁,这就需要使用者对锁也要有一定了解,这显然加重了使用者的负担.那么有没有一种方式当创建一个变量的时候,每个线程对其进行访问的时候访问的是自己线程的变量呢?其实ThreaLocal就可

  • Java并发编程学习之Unsafe类与LockSupport类源码详析

    一.Unsafe类的源码分析 JDK的rt.jar包中的Unsafe类提供了硬件级别的原子操作,Unsafe里面的方法都是native方法,通过使用JNI的方式来访问本地C++实现库. rt.jar 中 Unsafe 类主要函数讲解, Unsafe 类提供了硬件级别的原子操作,可以安全的直接操作内存变量,其在 JUC 源码中被广泛的使用,了解其原理为研究 JUC 源码奠定了基础. 首先我们先了解Unsafe类中主要方法的使用,如下: 1.long objectFieldOffset(Field

  • SPRING BOOT启动命令参数及源码详析

    前言 使用过Spring Boot,我们都知道通过java -jar可以快速启动Spring Boot项目.同时,也可以通过在执行jar -jar时传递参数来进行配置.本文带大家系统的了解一下Spring Boot命令行参数相关的功能及相关源码分析. 命令行参数使用 启动Spring Boot项目时,我们可以通过如下方式传递参数: java -jar xxx.jar --server.port=8081 默认情况下Spring Boot使用8080端口,通过上述参数将其修改为8081端口,而且通

  • Java1.8中StringJoiner的使用及源码详析

    前言 StringJoiner是Java里1.8新增的类,主要是帮助我们把一个列表拼接字符串, 或许有一部分人没有接触过. 所以本文将从使用例子入手, 分析StringJoiner的源码. 基本好的同学, 其实只要把这段例子自己运行一下, 自己看看源码就可以了.因为我觉得这个类挺简单的. 没必要看我下面的废话.... public class StringJoinerTest { public static void main(String[] args) { StringJoiner join

  • Java基于JDK 1.8的LinkedList源码详析

    前言 上周末我们一起分析了ArrayList的源码并进行了一些总结,因为最近在看Collection这一块的东西,下面的图也是大致的总结了Collection里面重要的接口和类,如果没有意外的话后面基本上每一个都会和大家一起学习学习,所以今天也就和大家一起来看看LinkedList吧! 2,记得首次接触LinkedList还是在大学Java的时候,当时说起LinkedList的特性和应用场景:LinkedList基于双向链表适用于增删频繁且查询不频繁的场景,线程不安全的且适用于单线程(这点和Ar

  • Spring Security架构以及源码详析

    前言 现在流行的通用授权框架有apache的shiro和Spring家族的Spring Security,在涉及今天的微服务鉴权时,需要利用我们的授权框架搭建自己的鉴权服务,今天总理了Spring Security. Spring Security 主要实现了Authentication(认证,解决who are you? ) 和 Access Control(访问控制,也就是what are you allowed to do?,也称为Authorization).Spring Securit

  • Python中的 enum 模块源码详析

    起步 上一篇 <Python 的枚举类型> 文末说有机会的话可以看看它的源码.那就来读一读,看看枚举的几个重要的特性是如何实现的. 要想阅读这部分,需要对元类编程有所了解. 成员名不允许重复 这部分我的第一个想法是去控制 __dict__ 中的 key .但这样的方式并不好,__dict__ 范围大,它包含该类的所有属性和方法.而不单单是枚举的命名空间.我在源码中发现 enum 使用另一个方法.通过 __prepare__ 魔术方法可以返回一个类字典实例,在该实例 使用 __prepare__

随机推荐