Springboot拦截器使用及其底层源码剖析

news2024/11/19 7:26:35

博主最近看了一下公司刚刚开发的微服务,准备入手从基本的过滤器以及拦截器开始剖析,以及在帮同学们分析一下上次的jetty过滤器源码与本次Springboot中tomcat中过滤器的区别。正题开始,拦截器顾名思义是进行拦截请求的一系列操作。先给大家示例一下使用操作

1 @Configuration
2 public class WebConfiguration implements WebMvcConfigurer {
3 
4     @Override
5     public void addInterceptors(InterceptorRegistry registry) {
6         registry.addInterceptor(new TstCfg());
7     }
8 }

 1 /**
 2  * @title: TstCfg
 3  * @Author junyu
 4  * 旧巷里有一个穿着白衬衫笑起来如太阳般温暖我的少年。
 5  * 记忆里有一个穿着连衣裙哭起来如孩子般讨人喜的女孩。
 6  * 他说,哪年树弯了腰,人见了老,桃花落了白发梢,他讲的笑话她还会笑,那便是好。
 7  * 她说,哪年国改了号,坟长了草,地府过了奈何桥,她回头看时他还在瞧,就不算糟。
 8  * @Date: 2020/7/29 11:53
 9  * @Version 1.0
10  */
11 public class TstCfg extends HandlerInterceptorAdapter {
12 
13     @Override
14     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
15         System.out.println("前");
16         return super.preHandle(request, response, handler);
17     }
18 
19     @Override
20     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
21         System.out.println("后");
22     }
23 
24     @Override
25     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
26         System.out.println("一直会出现");
27         System.out.println(1/0);
28     }
29 }

  首先我们可能会想到,我们的拦截器是何时装配到拦截器数组中

  其实就是在springboot启动时执行doCreateBean时,进行调用创建的org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration会在这里放入进去所有实现了WebMvcConfigurer接口的类,一共有7个,其中就有我们自己实现了WebMvcConfigurer接口的WebConfiguration类,

  我们的写的配置类WebConfiguration,继承了WebMvcConfigurer并重写了addInterceptors方法,所以我们的拦截器就在这时候装配进去了。这次知道为什么我们写的配置拦截器的配置示例需要继承------WebMvcConfigurer,我们当然也可以去继承已经实现了这个类的其他类,因为都可以去添加拦截器,博主亲试过,所以就不贴图了!

 

   好了,拦截器已经添加完了,那什么时候调用我们拦截器呢?一步一步脚印来,当浏览器请求我们地址的 时候,分一下几步:

 第一步:tomcat容器首先会接受到请求,这里将会走DispatcherServlet,看到这个大家都熟悉了。

 

 第二步:当然不会先走我们的拦截器了,我们的拦截器是在Springboot框架进行管理的,现在还在servlet,所以会先走到filter过滤器这一步,来贴图:官方代码太长,一屏截不下,前面有一个创建过滤器链的过程:等下次在给大家讲一下jetty的过滤器链与tomcat的过滤器链的区别

ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

 第三步:所以一旦连过滤器都没通过的话,会直接return回去,不会再进行拦截器的调用。来贴代码,过滤器通过后如何调用我们拦截器的

 1 private void internalDoFilter(ServletRequest request,
 2                                   ServletResponse response)
 3         throws IOException, ServletException {
 4         //这里将会调用所有过滤器链的过滤器,不做重点讲解了,看看下面拦截器的调用
 5         // Call the next filter if there is one
 6         if (pos < n) {
 7             ApplicationFilterConfig filterConfig = filters[pos++];
 8             try {
 9                 Filter filter = filterConfig.getFilter();
10 
11                 if (request.isAsyncSupported() && "false".equalsIgnoreCase(
12                         filterConfig.getFilterDef().getAsyncSupported())) {
13                     request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
14                 }
15                 if( Globals.IS_SECURITY_ENABLED ) {
16                     final ServletRequest req = request;
17                     final ServletResponse res = response;
18                     Principal principal =
19                         ((HttpServletRequest) req).getUserPrincipal();
20 
21                     Object[] args = new Object[]{req, res, this};
22                     SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
23                 } else {
24                     filter.doFilter(request, response, this);
25                 }
26             } catch (IOException | ServletException | RuntimeException e) {
27                 throw e;
28             } catch (Throwable e) {
29                 e = ExceptionUtils.unwrapInvocationTargetException(e);
30                 ExceptionUtils.handleThrowable(e);
31                 throw new ServletException(sm.getString("filterChain.filter"), e);
32             }
33             return;
34         }
35 
36         // We fell off the end of the chain -- call the servlet instance
37         try {
38             if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
39                 lastServicedRequest.set(request);
40                 lastServicedResponse.set(response);
41             }
42 
43             if (request.isAsyncSupported() && !servletSupportsAsync) {
44                 request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
45                         Boolean.FALSE);
46             }
47             // Use potentially wrapped request from this point
48             if ((request instanceof HttpServletRequest) &&
49                     (response instanceof HttpServletResponse) &&
50                     Globals.IS_SECURITY_ENABLED ) {
51                 final ServletRequest req = request;
52                 final ServletResponse res = response;
53                 Principal principal =
54                     ((HttpServletRequest) req).getUserPrincipal();
55                 Object[] args = new Object[]{req, res};
56                 SecurityUtil.doAsPrivilege("service",
57                                            servlet,
58                                            classTypeUsedInService,
59                                            args,
60                                            principal);
61             } else {
62                 //过滤器终于完事了,现在终于开始正式调用我们的方法了,我们看看service方法做了什么吧!
63                 servlet.service(request, response);
64             }
65         } catch (IOException | ServletException | RuntimeException e) {
66             throw e;
67         } catch (Throwable e) {
68             e = ExceptionUtils.unwrapInvocationTargetException(e);
69             ExceptionUtils.handleThrowable(e);
70             throw new ServletException(sm.getString("filterChain.servlet"), e);
71         } finally {
72             if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
73                 lastServicedRequest.set(null);
74                 lastServicedResponse.set(null);
75             }
76         }
77     }    

其实最终它会调用到DispatcherServlet的doDispatch方法

 1 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
 2         HttpServletRequest processedRequest = request;
 3         HandlerExecutionChain mappedHandler = null;
 4         boolean multipartRequestParsed = false;
 5 
 6         WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
 7 
 8         try {
 9             ModelAndView mv = null;
10             Exception dispatchException = null;
11 
12             try {
13                 processedRequest = checkMultipart(request);
14                 multipartRequestParsed = (processedRequest != request);
15 
16                 // Determine handler for the current request.
17                 mappedHandler = getHandler(processedRequest);
18                 if (mappedHandler == null) {
19                     noHandlerFound(processedRequest, response);
20                     return;
21                 }
22 
23                 // Determine handler adapter for the current request.
24                 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
25 
26                 // Process last-modified header, if supported by the handler.
27                 String method = request.getMethod();
28                 boolean isGet = "GET".equals(method);
29                 if (isGet || "HEAD".equals(method)) {
30                     long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
31                     if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
32                         return;
33                     }
34                 }
35                 //所有拦截器开始在调用方法前拦截,如果你拦截器中返回false,则直接return不会再调用该方法!下面有源代码
36                 if (!mappedHandler.applyPreHandle(processedRequest, response)) {
37                     return;
38                 }
39 
40                 // Actually invoke the handler.
41                 //底层进行invoke反射,调用当前请求的方法,不用再往里面看了
42                 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
43 
44                 if (asyncManager.isConcurrentHandlingStarted()) {
45                     return;
46                 }
47 
48                 applyDefaultViewName(processedRequest, mv);
49                 //调用拦截器的postHandle,下面有源代码
50                 mappedHandler.applyPostHandle(processedRequest, response, mv);
51             }
52             catch (Exception ex) {
53                 dispatchException = ex;
54             }
55             catch (Throwable err) {
56                 // As of 4.3, we're processing Errors thrown from handler methods as well,
57                 // making them available for @ExceptionHandler methods and other scenarios.
58                 dispatchException = new NestedServletException("Handler dispatch failed", err);
59             }
60             //该方法中多做了一些逻辑,其实最后也调用了triggerAfterCompletion方法,最终调用拦截器方法的afterCompletion方法
61             processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
62         }
63         catch (Exception ex) {
64             //所以不管是否出现异常,拦截器方法的afterCompletion方法是一定会调用的!
65             triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
66         }
67         catch (Throwable err) {
68             //所以不管是否出现异常,拦截器方法的afterCompletion方法是一定会调用的!
69             triggerAfterCompletion(processedRequest, response, mappedHandler,
70                     new NestedServletException("Handler processing failed", err));
71         }
72         finally {
73             if (asyncManager.isConcurrentHandlingStarted()) {
74                 // Instead of postHandle and afterCompletion
75                 if (mappedHandler != null) {
76                     mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
77                 }
78             }
79             else {
80                 // Clean up any resources used by a multipart request.
81                 if (multipartRequestParsed) {
82                     cleanupMultipart(processedRequest);
83                 }
84             }
85         }
86     }

现在终于开始了我们拦截器的方法了,一个一个来:

 1 boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
 2         HandlerInterceptor[] interceptors = getInterceptors();
 3         if (!ObjectUtils.isEmpty(interceptors)) {
 4             for (int i = 0; i < interceptors.length; i++) {
 5                 HandlerInterceptor interceptor = interceptors[i];
 6                 //调用所有拦截器的preHandle方法
 7                 if (!interceptor.preHandle(request, response, this.handler)) {
 8                     //就算preHandle方法没有通过,仍然会调用这个triggerAfterCompletion方法。
 9                     triggerAfterCompletion(request, response, null);
10                     return false;
11                 }
12                 this.interceptorIndex = i;
13             }
14         }
15         return true;
16     }

 1 void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
 2             throws Exception {
 3 
 4         HandlerInterceptor[] interceptors = getInterceptors();
 5         if (!ObjectUtils.isEmpty(interceptors)) {
 6             for (int i = interceptors.length - 1; i >= 0; i--) {
 7                 HandlerInterceptor interceptor = interceptors[i];
 8                 //调用拦截器的postHandle方法,
 9                 interceptor.postHandle(request, response, this.handler, mv);
10             }
11         }
12     }

 1 void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
 2             throws Exception {
 3 
 4         HandlerInterceptor[] interceptors = getInterceptors();
 5         if (!ObjectUtils.isEmpty(interceptors)) {
 6             for (int i = this.interceptorIndex; i >= 0; i--) {
 7                 HandlerInterceptor interceptor = interceptors[i];
 8                 try {
 9                     //调用拦截器的afterCompletion方法,不管是否异常都会进行调用,但是如果该方法报异常,会被抓住。
10                     //不会影响程序正常运行,只会打印出来
11                     interceptor.afterCompletion(request, response, this.handler, ex);
12                 }
13                 catch (Throwable ex2) {
14                     logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
15                 }
16             }
17         }
18     }

下面这个就是打印了一下,但是不会影响我们的请求响应回去:

 还是会正常响应回客户端:

 好了,到此拦截器的实现以及源码分析流程到此结束,本来想给大家从Springboot的reflash方法开始解析拦截器,但是内容太多了,不仅跑题而且博主也一时半会给大家无法讲解明白。

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

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

相关文章

Java学习 - 网络TCP,UDP协议讲解

TCP协议 TCP协议特点 面向连接 应用程序在使用TCP协议之前&#xff0c;必须先建立TCP连接在数据传输完毕后&#xff0c;必须释放已经建立的TCP连接类似于打电话 点对点通信 TCP协议就像打电话&#xff0c;只能一对一&#xff0c;不能一对多或多对多 可靠传输 TCP协议能够保证…

分布式锁实现方案-基于Redis实现的分布式锁

目录 一、基于Lua看门狗实现 1.1 缓存实体 1.2 延迟队列存储实体 1.3 分布式锁RedisDistributedLockWithDog 1.4 看门狗线程续期 1.5 测试类 1.6 测试结果 1.7 总结 二、RedLock分布式锁 2.1 Redlock分布式锁简介 2.2 RedLock测试例子 2.3 RedLock 加锁核心源码分析…

OpenAI CTO谈GPT-5将达博士生智力水平;斯坦福评估排名前十两款来自中国

&#x1f989; AI新闻 &#x1f680; OpenAI CTO谈GPT-5将达博士生智力水平 摘要&#xff1a;美国达特茅斯工程学院采访了OpenAI首席技术官米拉・穆拉蒂&#xff0c;她表示GPT-4的智力相当于高中生&#xff0c;而GPT-5将在一年半后发布&#xff0c;预计达到博士生水平。穆拉蒂…

【Unity Shader】Alpha Blend(Alpha混合)的概念及其使用示例

在Unity和图形编程中&#xff0c;Alpha Blend&#xff08;也称为Alpha混合&#xff09;是一种用于处理像素透明度的技术。它允许像素与背景像素融合&#xff0c;从而实现透明或半透明的效果。Alpha Blend在渲染具有透明度的物体&#xff08;如窗户、玻璃、水、雾等&#xff09;…

闷热烦躁,精神倦怠?3个食疗方,5款好物,助您清凉度夏~

夏日&#xff0c;阳气旺盛&#xff0c;万物繁荣秀丽&#xff0c;但赤日炎炎似火烧&#xff0c;人们容易闷热、食欲不佳、四肢倦怠、萎靡不振等&#xff0c;再加上蚊虫多&#xff0c;更是让人烦躁不安&#xff01; 5款清凉好物 赶走蚊虫 天气炎热&#xff0c;蚊子、小虫都来了…

webpack【实用教程】

基础配置 配置的拆分和合并 通常 webpack 的配置文件会有3个 webpack.common.js 公共配置&#xff08;会被另外两个配置文件导入并合并&#xff09;webpack.dev.js 开发环境的配置webpack.prod.js 生产环境的配置 开发环境的本地服务 在 webpack.dev.js 中配置 devServer:…

springboot加载bean的方式

在SpringBoot的大环境下&#xff0c;基本上很少使用之前的xml配置Bean&#xff0c;主要是因为这种方式不好维护而且也不够方便。 springboto注入bean主要采用下图几种方式&#xff0c; 1、注解装配Bean 1、使用Component等派生注解 只要在类上加类上加 Component 注解即可,该…

Kotlin 运行代码片段多种方式

目录 场景描述 一、Scratch files and worksheets in the IDE 1、Scratch files(草稿文件) 特点&#xff1a; Scratch files文件创建步骤&#xff1a; 功能解释&#xff1a; Scratch Buffer笔记文件&#xff1a; 2、Worksheets(工单) 1&#xff09;、创建方式不同。 …

基于堆叠长短期记忆网络 Stacked LSTM 预测A股股票价格走势

前言 系列专栏:【深度学习&#xff1a;算法项目实战】✨︎ 涉及医疗健康、财经金融、商业零售、食品饮料、运动健身、交通运输、环境科学、社交媒体以及文本和图像处理等诸多领域&#xff0c;讨论了各种复杂的深度神经网络思想&#xff0c;如卷积神经网络、循环神经网络、生成对…

思看科技冲刺上市疑云:募资用途遭强烈质疑,IPO前突击分红

近日&#xff0c;思看科技&#xff08;杭州&#xff09;股份有限公司&#xff08;下称“思看科技”&#xff09;已更新提交2023年最新财务资料&#xff0c;重启科创板IPO进程。贝多财经了解到&#xff0c;思看科技的上市申请于2023年6月获上交所受理&#xff0c;目前已进入问询…

简鹿文件批量重命名:一款文件批量改名高手都在用的工具

作为 IT 行业的搬砖民工&#xff0c;互联网的数据量爆炸性增长&#xff0c;文件管理成为了一项日益重要的任务。"简鹿文件批量重命名"应运而生&#xff0c;旨在为用户提供一个高效、灵活的解决方案&#xff0c;以应对繁琐的文件命名、排序、创建及属性修改等挑战。 这…

足底筋膜炎吃什么药最管用

足底筋膜炎在使用药物后未见明显改善&#xff0c;但通过使用“古顺、敷堂、筋膜”贴后病情得到了缓解。按疗程使用自愈了&#xff0c;这款筋膜贴通过其药物成分渗透到组织中&#xff0c;从根本上消除炎症&#xff0c;从而快速缓解症状&#xff0c;足底筋膜炎得到了明显的改善。…

【思科】IPv6 过渡技术 - 6to4隧道

【思科】IPv6 过渡技术 - 6to4隧道 实验要求实现思路6 to 4 特点注意点IPv4 转 IPv6 格式小技巧 配置R1基础配置OSPFv3 局域网可达 R2基础配置局域网环境(OSPFv3)&#xff1a;IPv6 网络6 to 4 隧道 R3R4基础配置局域网环境(OSPFv3)&#xff1a;IPv6 网络6 to 4 隧道 R5基础配置…

Apifox 快速入门教程

访问示例项目​ 可访问Apifox官网&#xff0c;下载并打开 Apifox 后&#xff0c;你将会看到由系统自动创建的“示例团队”&#xff0c;其中内含一个“示例项目”。 项目中自动生成了与宠物商店有关的数条接口。 手动新建接口​ 新建接口是开发者们最常用的功能之一。Apifox 能…

Java研学-RBAC权限控制(八)

九 登录登出 1 登录作用 判断员工是否有权限访问&#xff0c;首先得知道现在操作的人是谁&#xff0c;所以必须先实现登录功能 2 登录流程 ① 提供登录页面&#xff0c;可输入用户名与密码信息&#xff0c;并添加执行登录的按钮。&#xff08;登录页面不能被拦截&#xff09;…

微服务、多租户、单点登录、国产化形成的开源Java框架!

一、项目简介 JVS是软开企服构建的一站式数字化的开源框架&#xff0c;支持对接多种账户体系&#xff0c;支持多租户、支持Auth2、统一登录、单点登录等&#xff0c;支持原生开发、低代码/零代码开发应用。 二、框架核心功能 控制台(首页)&#xff1a;采用配置化的方式 用户…

C语言从入门到进阶(15万字总结)

前言&#xff1a; 《C语言从入门到进阶》这本书可是作者呕心沥血之作&#xff0c;建议零售价1元&#xff0c;当然这里开个玩笑。 本篇博客可是作者之前写的所有C语言笔记博客的集结&#xff0c;本篇博客不止有知识点&#xff0c;还有一部分代码练习。 有人可能会问&#xff…

HarmonyOS Next 系列之可移动悬浮按钮实现(六)

系列文章目录 HarmonyOS Next 系列之省市区弹窗选择器实现&#xff08;一&#xff09; HarmonyOS Next 系列之验证码输入组件实现&#xff08;二&#xff09; HarmonyOS Next 系列之底部标签栏TabBar实现&#xff08;三&#xff09; HarmonyOS Next 系列之HTTP请求封装和Token…

Huffman树——AcWing 148. 合并果子

目录 Huffman树 定义 运用情况 注意事项 解题思路 AcWing 148. 合并果子 题目描述 运行代码 代码思路 其它代码 代码思路 Huffman树 定义 它是一种最优二叉树。通过构建带权路径长度最小的二叉树&#xff0c;经常用于数据压缩等领域。 运用情况 在数据压缩中&a…

C语言 while循环1

在C语言里有3种循环&#xff1a;while循环 do while 循环 for循环 while语句 //while语法结构 while&#xff08;表达式&#xff09;循环语句; 比如在屏幕上打印1-10 在while循环中 break用于永久的终止循环 在while循环中&#xff0c;continue的作用是跳过本次循环 …