关于springboot的拦截器能力源码分析

news2024/11/25 10:24:41

首先你得有web环境,这个就不说了,springboot下很简单。

一、拦截器使用

我们先来使用一下拦截器。

步骤1、先创建一个Controller

@RestController
@RequestMapping("/test")
public class MyController {

    @GetMapping("/test/{name}")
    public String test(@PathVariable(name = "name") String name) {
        System.out.println("MyController.test invoke:" + name);
        return name;
    }
}

逻辑很简单,就是一个get请求,携带一个name参数,并且把这个name返回给前端。

步骤2、创建拦截器

// 拦截器,只需要实现 HandlerInterceptor 接口即可
public class MyInterceptor implements HandlerInterceptor {
    // 请求处理之前触发,如果返回 true,则继续执行往下走,否则就直接返回
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyInterceptor.preHandle invoke");
        String requestURI = request.getRequestURI();
        if (requestURI.contains("/test")) {
            return true;
        }
        return false;
    }

    // 请求处理之后触发,如果 preHandle 返回 true,则执行 postHandle
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("MyInterceptor.postHandle invoke");
    }

    // 请求处理之后触发,如果 preHandle 返回 true,则执行 afterCompletion
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyInterceptor.afterCompletion invoke");
    }
}

步骤3、配置类,加入拦截器

以前我们在Spring Mvc的时候要配置一大堆映射。这里在boot环境我们只需要配置在配置类里面即可。

// 开启一个配置类,实现WebMvcConfigurer即可
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    // 添加拦截器 这里我是用的new 但是实际不对,最好是使用注入进来的单例,不然开销太大了
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry
                // 添加第一组拦截器和他的规则,我们就添加我们的MyInterceptor,并且他的拦截规则是/**,也就是拦截所有的请求
                // 而且我们放行css和js等静态资源,因为这些资源是不需要拦截的
                .addInterceptor(new MyInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/css/**", "/js/**");
    }
}

步骤四、启动测试

在这里插入图片描述
我们看到页面返回没问题。我们再来看看后端控制台。

MyInterceptor.preHandle invoke
MyController.test invoke:levi
MyInterceptor.postHandle invoke
MyInterceptor.afterCompletion invoke

不出所料,我们看到方法执行前触发Interceptor.preHandle
然后是方法执行,然后是Interceptor.postHandle
最后是Interceptor.afterCompletion
所以我们这就基本可以说使用起来了。具体能干啥,我们下文再说。我们先来看源码逻辑。知道他的底层实现。

二、拦截器底层源码原理

1、源码分析

我们知道在MVC里面对于拦截器的处理是在DispatcherServlet这个类中处理的,他处理了请求的映射和派发等逻辑。
我们就来看他的核心逻辑。
DispatcherServlet本质还是Servlet,既然是Servlet那他的核心就在doService()方法里面,我们先来看这个方法。

org.springframework.web.servlet.DispatcherServlet#doService

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        
	***省略上面无用***
	this.doDispatch(request, response);
     ***省略下面无用***  
}

按照spring的毛病,所有的核心业务都在以do开头的方法实现中,那我们就进入这个doDispatch看看。

org.springframework.web.servlet.DispatcherServlet#doDispatch

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        省略没用的
        try {
            try {
                ModelAndView mv = null;
                Exception dispatchException = null;

                try {
                	// 校验文件上传,这里不看
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    // 获取处理器,这里其实就是获取你前端的接口路径交给哪个controller的
                    // 哪个方法去执行的派发器,这里不是重点也不说
                    mappedHandler = this.getHandler(processedRequest);
                    // 获取适配器,这个就是找到了执行的方法
                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());              
                  	
                  	// 下面就是执行方法,上面就是找到执行器,这个处于中间,所以他必然是处理拦截器的preHandle的地方,
                  	// 所以我们就知道这里是拦截器生效的地方,于是我们来到这里。
                  	if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
                  
					// 这里是执行方法的地方,也就是执行你controller的里面的方法的
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    
                    this.applyDefaultViewName(processedRequest, mv);
                    // 当上面的方法被放行通过之后,就来这里执行applyPostHandle方法。
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
					// 在这里最后执行afterCompletion方法
					this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
                } 
        } 
        省略没用的
    }

上面我们看到我们推出拦截器执行的机会是mappedHandler.applyPreHandle这个方法,所以我们就来看这个方法。

org.springframework.web.servlet.HandlerExecutionChain#applyPreHandle
OK,也不出所料,我们在代码上来就看到了interceptors 获取了所有的拦截器。

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
   // 获取所有的拦截器,封装在这里
   HandlerInterceptor[] interceptors = this.getInterceptors();
   // null校验
   if (!ObjectUtils.isEmpty(interceptors)) {
       // 遍历所有拦截器,并且给当前遍历到的哪个拦截器保存一个下标进度interceptorIndex 
       for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
           HandlerInterceptor interceptor = interceptors[i];
           /** 
              挨个开始执行每个拦截器的preHandle方法,并且返回布尔值,我们看到返回为false的时候取反为true成立,此时就会执行triggerAfterCompletion方法,这个方法的内容就是执行拦截器的interceptor.afterCompletion方法。当preHandle执行结果为true的时候,也就是我们在拦截器里面返回了true的时候,这里就直接跳过去了,等于只执行了preHandle。
              所以我们可以知道,一旦有一个拦截器返回false,后面的就都不走了。
		   */
           if (!interceptor.preHandle(request, response, this.handler)) {
           	   /**
				我们这里看一下triggerAfterCompletion,其实就是拿到之前执行过的所有的拦截器,
				就是之前执行成功的,
				也就是返回为true的.此时通过interceptorIndex这个下标反向遍历这些拦截器,
				因为是反向遍历的,所以我们知道afterCompletion是和preHandle倒序执行的
				把之前执行成功的拦截器的afterCompletion方法执行一遍。这里我们说一下,
				因为afterCompletion这个方法是一定会被执行的,
				所以以前成功的就要执行afterCompletion,以后失败的也就不说了,直接就返回了
				啥都不执行了。
               */
               this.triggerAfterCompletion(request, response, (Exception)null);
               return false;
           }
       }
   }
   return true;
}

我们这里看一下triggerAfterCompletion,其实就是拿到之前执行过的所有的拦截器,就是之前执行成功的,也就是返回为true的
此时通过interceptorIndex这个下标反向遍历这些拦截器,把之前执行成功的拦截器的afterCompletion方法执行一遍。因为是反向遍历的,所以我们知道afterCompletion是和preHandle倒序执行的
这里我们说一下,因为afterCompletion这个方法是一定会被执行的,所以以前成功的就要执行afterCompletion,以后失败的也就不说了。
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) {
    HandlerInterceptor[] interceptors = this.getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for(int i = this.interceptorIndex; i >= 0; --i) {
            HandlerInterceptor interceptor = interceptors[i];
			interceptor.afterCompletion(request, response, this.handler, ex);
        }
    }
}

以上就是拦截器在boot中的生效能力。

2、总结

我们这里总结一下他的这个原理,首先他会获取你容器中的所有拦截器,包括你自己实现的拦截器,组成一个集合。然后再一个方法被拦截的时候,他会遍历这个拦截器,然后挨个执行拦截器的preHandle方法,在每个拦截器执行自己的preHandle方法拿到返回值,返回值如果是true表示通过放行,然后就往下走,执行后面的拦截器的preHandle。
如果执行结果为false,表明没通过拦截器的拦截方法。此时整个拦截器链条就直接断了,他会去执行一个叫做triggerAfterCompletion的方法,在这个方法里面,会把以前执行过preHandle的方法执行一遍他的afterCompletion方法。
如果每个拦截器都顺利执行了preHandle(都返回true),那么此时他就往下走执行方法本身。
然后执行完方法之后,在执行每个拦截器的PostHandle方法。
最后执行每个拦截器的afterCompletion方法。
也就是说我们看到其实preHandle和afterCompletion是必然会执行的,但是PostHandle不一定。

三、拦截器能做什么

以我们这里这个拦截器为例。

// 拦截器,只需要实现 HandlerInterceptor 接口即可
public class MyInterceptor implements HandlerInterceptor {
    // 请求处理之前触发,如果返回 true,则继续执行往下走,否则就直接返回
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyInterceptor.preHandle invoke");
        String requestURI = request.getRequestURI();
        if (requestURI.contains("/test")) {
            return true;
        }
        return false;
    }

    // 请求处理之后触发,如果 preHandle 返回 true,则执行 postHandle
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("MyInterceptor.postHandle invoke");
    }

    // 请求处理之后触发,如果 preHandle 返回 true,则执行 afterCompletion
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyInterceptor.afterCompletion invoke");
    }
}

我们可以在方法执行之前的preHandle这里拦截到哪些请求,对于这些请求我们判断是不是要先登录才能访问。那么我们这里就可以判断他是不是已经登录了,要是登录了,我们就返回true放行,否则就返回false,并且在返回false之前跳转去登录页面,或者返给前端一个标识,让前端去跳都可以。
当然也可以放行一些不需要登录就能访问的,反正你灵活使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2035908.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HAProxy原理及实例

目录 目录 haproxy简介 haproxy的基本信息 haproxy下载并查看版本 haproxy的基本配置信息 global配置 ​编辑多进程和多线程 启用多进程 启用多线程 haproxy开启多线程和多进程有什么用 proxies配置 defaults frontend backend listen socat工具 实例&#xff1a…

ESP32 SNTP 网络校时 钟表显示

8月12日(2) 例程环境&#xff1a;Windows 11、Visual Studio Code、IDF_V5.2.1、LVGL_V8.3.11、HelloBug ESP32 Pilot开发板 源码获取&#xff1a;https://item.taobao.com/item.htm?ftt&id652537645861 向商家索取对应源码 SNTP (Simple Network Time Protocol) 是一种简…

中科亿海微SoM模组——电机驱动板

电机驱动板 电机驱动板作为驱动电机的重要组成部分&#xff0c;被广泛应用于工业自动化、消费电子、汽车、家用电器等应用领域。在工业自动化中&#xff0c;电机驱动板主要用于控制机器人、数控机床、输送带等设备&#xff0c;确保其高效、精准地运行。在消费电子和家用电器中…

【ARM Coresight Debug 工具系列 -- Trace32 | ARM-DS5 | OpenOCD JLINK 关系与差】

请阅读【ARM Coresight SoC-400/SoC-600 专栏导读】 文章目录 常用debug工具差异介绍Trace32ARM DS-5OpenOCDJ-Link 关系与差异差异 示例比较使用 Trace32 进行实时跟踪使用 ARM DS-5 进行高级调试使用 OpenOCD 进行开源调试 Summary 常用debug工具差异介绍 在嵌入式系统开发和…

阿里淘天校招校招开始啦,欢迎投递~

淘天校招&校招开始啦&#xff0c;欢迎投递~ 后续继续推出技术类面试资料&#xff0c;有问题也可咨询哦&#xff01; 校招内推码&#xff08;25年10月前均有效&#xff09; 社招内推码&#xff08;长期有效&#xff09;

Tarjan(五)vDCC缩点

Tarjan(五) vDCC点双联通分量&#xff1a; 需要之前的前置知识&#xff0c;需要搞懂什么是割点。在tarjan(2)中有介绍到。 点双连通分量是指在一个无向图中&#xff0c;如果一个子图是点双连通的&#xff08;即去掉该子图中的任意一个节点后&#xff0c;剩余的图仍然是连通的&a…

电商平台产品ID|CDN与预渲染|前端边缘计算

技术实现 都是通过ID拿到属性&#xff0c;进行预渲染html&#xff0c;通过 oss 分发出去 详情页这种基本都是通过 ssr 渲染出来&#xff0c;然后上缓存 CDN 分发到边缘节点来处理&#xff0c;具体逻辑可以参考 淘宝——EdgeRoutine边缘计算&#xff08;CDNServerless 边缘计算…

深度解析HAProxy:构建高可用负载均衡的终极指南

目录 haproxy配置文件组成 实验环境 haproxy安装 haproxy的配置文件说明 全局配置段global 多进程和多线程配置 代理配置段proxies server配置说明 实验相关配置 测试效果&#xff1a; haproxy的状态页 socat命令 socat命令的一些常用示例 HAProxy的调度算法 静…

Oracle事务是怎么练成的

什么是事务 事务是数据库管理系统执行过程的一个逻辑单位&#xff0c;由一系列有限的数据库操作序列构成&#xff0c;事务必须满足‌ACID属性。ACID理论是数据库中最重要的概念之一&#xff0c;分别代表原子性&#xff08;Atomicity&#xff09;、一致性&#xff08;Consisten…

人工智能GPU算力评估分析

GPU算力评估 一、 关于训练GPU的带宽 大模型训练算力需求&#xff1a;总算力(Tlops)6倍模型参数量训练数据token量&#xff0c;精准高效满足大规模训练需求。 需要把那么计算量和通信量的比例是多少&#xff1f; 3&#xff1a;指的是一次正向两次反向&#xff0c;反向是梯度…

程序员职场升级攻略:学AI技能,稳步迈向月薪破万之路

在人工智能高速发展的今天&#xff0c;AI技术已经成为职场人士提升收入的有力武器。许多人通过学习AI技能&#xff0c;成功跻身高收入行业&#xff0c;实现了月薪破万的目标。本文将揭秘高收入行业与城市&#xff0c;并提供一条清晰的学习路线&#xff0c;助你成为AI领域的一员…

ubuntu:更新阿里云apt源

前言 我用vmware也搭建了ubuntu服务器&#xff0c;并同样发现apt几乎完全用不了&#xff08;系统默认用的是清华源&#xff0c;可能较老了&#xff09; 更新阿里云apt源 1、去阿里云官网找系统对应的apt源配置 阿里云镜像&#xff1a;阿里巴巴开源镜像站-OPSX镜像站-阿里云开发…

Unity教程(九)角色攻击的改进

Unity开发2D类银河恶魔城游戏学习笔记 Unity教程&#xff08;零&#xff09;Unity和VS的使用相关内容 Unity教程&#xff08;一&#xff09;开始学习状态机 Unity教程&#xff08;二&#xff09;角色移动的实现 Unity教程&#xff08;三&#xff09;角色跳跃的实现 Unity教程&…

WindowsAPI 查阅笔记:进程间管道通信

进程间有名管道的通信&#xff1a; 1.1 重叠I/O&#xff08;Overlapped I/O&#xff09; 重叠I/O&#xff08;Overlapped I/O&#xff09;是Windows编程中的一种异步 I / O 处理方式&#xff0c;它允许程序在发出I/O请求后继续执行其他任务&#xff0c;而不必等待I/O操作完成…

萌啦定价工具,萌啦数据ozon定价工具

在电商行业日益竞争激烈的今天&#xff0c;精准定价成为了商家们获取市场竞争优势的关键一环。尤其是对于在Ozon平台上耕耘的卖家而言&#xff0c;无论是本土卖家还是跨境商家&#xff0c;如何快速、准确地制定出既符合市场需求又能保障利润的价格策略&#xff0c;成为了亟待解…

高防服务器的机制和原理

高防服务器是一种具备强大防御能力的服务器&#xff0c;旨在保护网站免受各种网络攻击&#xff0c;如DDoS&#xff08;分布式拒绝服务&#xff09;攻击、CC&#xff08;ChallengeCollapsar&#xff09;攻击等。今天小编将从流量过滤与清洗、负载均衡与反向代理、实时监控与报警…

圈内水刊“三巨头”之首实至名归?发文量飙升至9000+,硕博小白照样发1区TOP!

【SciencePub学术】昨天&#xff0c;小编给大家介绍了环境水刊“三巨头”之一的《Journal of Hazardous Materials》&#xff0c;本期&#xff0c;给大家带来的是位于环境水刊“三巨头”之首的《Science of the Total Environment》&#xff0c;属于JCR1区中科院1区TOP&#xf…

冷数据归档(历史库),成本与性能如何兼得?| OceanBase应用实践

随着数据量的迅猛增长&#xff0c;企业和组织在数据库管理方面遭遇的挑战愈发凸显。数据库性能逐渐下滑、存储成本节节攀升&#xff0c;以及数据运维复杂性的增加&#xff0c;这些挑战使得DBA和开发者在数据管理上面临更大的压力。 为了应对这些挑战&#xff0c;对数据生命周期…

vulnstack-5

环境搭建 靶场虚拟机共用两个&#xff0c;一个外网一个内网&#xff0c;用来练习红队相关内容和方向&#xff0c;主要包括常规信息收集、Web攻防、代码审计、漏洞利用、内网渗透以及域渗透等相关内容学习。 虚拟机密码 win7 sun\heart 123.com sun\Administrator dc123.com # …

华为软件测试笔试真题,赶快收藏

软件测试工程师笔试题目 一&#xff0e;填空 1、 系统测试使用&#xff08; C &#xff09;技术, 主要测试被测应用的高级互操作性需求, 而无需考虑被测试应用的内部结构。 A、 单元测试 B、 集成测试 C、 黑盒测试 D、白盒测试 2、单元测试主要的测试技术不包括&#xff08;…