Spring Boot Interceptor(拦截器使用及原理)

之前的博客中讲解了关于 Spring AOP的思想和原理,而实际开发中Spring Boot对于AOP的思想的具体实现就是Spring Boot Interceptor。在 Spring Boot 应用程序开发中,拦截器(Interceptor)是一个非常有用的工具。它允许我们在 HTTP 请求到达 Controller 之前或响应离开 Controller 之后执行一些自定义逻辑。本文将介绍 Spring Boot 中如何使用拦截器,并提供一些实际的使用示例。

我们还是以对用户是否登录进行校验这个场景来展开叙述。


1 不使用拦截器的用户登陆验证

比如我们的UserController中有多个方法,那么执行这些方法之前肯定要对用户是否登录进行校验。每个⽅法中都要单独写⽤户登录验证的⽅法,即使封装成公共⽅法,也⼀样要传参调⽤和在⽅法中进⾏判断。 随着添加的控制器越来越多,调用用户登陆验证的方法也就越来越多,这样就会增加后期的修高成本和维护成本,并且这些代码和实际要实现的业务是没有关系的,实际开发中是不希望一些无关代码侵入业务代码的。

上面的问题可不就是AOP所解决的问题吗!所以提供一个公共的AOP方法来进行统一的用户登录验证是必要的,Spring Boot也为我们提供了使用的方法。

原生的Spring Boot AOP也可以通过前置通知或者环绕通知来进行拦截,但是在配饰拦截规则的时候使用的aspectj时十分不友好的,并且再拦截的时候想要排除一些特定的方法,比如登录和注册,那个拦截规则是很难定义的,甚至根本没办法实现。

对于以上问题 Spring 中提供了具体的实现拦截器:HandlerInterceptor。

2 Spring 拦截器

在 Spring Boot 应用程序开发中,拦截器(Interceptor)是一个非常有用的工具。它允许我们在 HTTP 请求到达 Controller 之前或响应离开 Controller 之后执行一些自定义逻辑。拦截器的实现可以分为以下两个步骤:

  1. 创建自定义拦截器:实现HandlerInterceptor接口并重写接口的preHander方法(执行具体方法之前的预处理方法)。
  2. 注册定义拦截器:将⾃定义拦截器加⼊ WebMvcConfigurer 的 addInterceptors ⽅法中。(自定义配置类实现WebMvcConfigurer 并且重写addInterceptors方法,重写addInterceptors方法的目的就是将自定义的拦截器注册到项目中,在这个过程中可以配置拦截规则)

2.1 创建自定义拦截器

首先,我们需要创建一个实现 HandlerInterceptor 接口的类。HandlerInterceptor 接口提供了三个方法:

  • preHandle:在请求处理之前调用
  • postHandle:在请求处理之后调用,但在视图渲染之前
  • afterCompletion:在整个请求完成之后调用,通常用于资源清理

现在的业务需求下我们只需要在重写preHandle方法即可:

/*自定义拦截器 实现HandlerInterceptor接口 */
@Component
public class LoginInterceptor implements HandlerInterceptor {// 调用目标方法之前执行的方法// 此方法返回 boolean 类型的值,//          如果返回的是true 表示(拦截器)验证成功,继续走后续的流程,执行目标方法//          如果返回的是 false 表示拦截器验证失败,后续的流程和目标方法就不执行。@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//用户登录判断业务HttpSession session = request.getSession(false);if(session != null && session.getAttribute("admin")!=null){System.out.println("登录了");// 用户已登录了return true;}//用户没有登录System.out.println("还没有登录");response.sendRedirect("/user/login");//可以重定向到系统的登录路由//response.setStatus(401);//向前端返回相应的状态码  401:没有权限return false;}
}

2.2 注册自定义拦截器

在自定义拦截器之后我们还需要将自定义的拦截器注册到系统的配置中。

addPathPatterns:表示需要拦截的 URL,“**”表示拦截任意⽅法(也就是所有⽅法)。
excludePathPatterns:表示需要排除的 URL。
UserController代码:
 
@RestController
@RequestMapping("user")
public class UserController {@GetMapping("login")public String login(HttpServletRequest req){System.out.println("执行了login!");// 登录成功,创建会话HttpSession session = req.getSession(true);session.setAttribute("admin","lisi");return "lisi login";}@GetMapping("reg")public String reg(){return "reg";}@GetMapping("index")public String index(HttpServletRequest req){HttpSession session = req.getSession(false);Object admin = session.getAttribute("admin");System.out.println(admin);return "index";}}
运行程序观察执行情况:

1. 没有登陆的状态下访问index:此时请求成功被拦截器拦截下来
2.访问login:此时lisi成功的登录,并且设置了session

3.登陆后再次访问index:触发拦截器但是通过了后端的验证,所以后端会打印“登录了”和session的value也即是lisi,同时返回给前端"index"。

结果:

这样一个通过拦截器实现用户登陆验证的简单案例就完成了。

3 拦截器实现原理

在没有加入拦截器的时候,程序的调用顺序如图所示:

 然⽽有了拦截器之后,会在调⽤ Controller 之前进⾏相应的业务处理,执⾏的流程如下图所示:

Spring 中的拦截器也是通过动态代理和环绕通知的思想实现的。

通过源码分析进一步理解拦截器的工作原理

所有的 Controller 执⾏都会通过⼀个调度器 DispatcherServlet 来实现,这⼀点可以从 Spring Boot 控制台的打印信息看出,如下图所示:

所有⽅法都会执⾏ DispatcherServlet 中的 doDispatch 调度⽅法,doDispatch 源码如下

@SuppressWarnings("deprecation")protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// Determine handler for the current request.mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// Determine handler adapter for the current request.HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.String method = request.getMethod();boolean isGet = HttpMethod.GET.matches(method);if (isGet || HttpMethod.HEAD.matches(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// Actually invoke the handler.mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}applyDefaultViewName(processedRequest, mv);mappedHandler.applyPostHandle(processedRequest, response, mv);}catch (Exception ex) {dispatchException = ex;}catch (Throwable err) {// As of 4.3, we're processing Errors thrown from handler methods as well,// making them available for @ExceptionHandler methods and other scenarios.dispatchException = new ServletException("Handler dispatch failed: " + err, err);}processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}catch (Exception ex) {triggerAfterCompletion(processedRequest, response, mappedHandler, ex);}catch (Throwable err) {triggerAfterCompletion(processedRequest, response, mappedHandler,new ServletException("Handler processing failed: " + err, err));}finally {if (asyncManager.isConcurrentHandlingStarted()) {// Instead of postHandle and afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}}

 

从上述源码可以看出在开始执⾏ Controller 之前,会先调⽤ 预处理⽅法 applyPreHandle,而applyPreHandle ⽅法的实现源码如下:
从上述源码可以看出,在 applyPreHandle 中会获取所有的拦截器 HandlerInterceptor 并执⾏拦截器中的 preHandle ⽅法,这样就会咱们前⾯定义的拦截器对应上了:

 此时⽤户登录权限的验证⽅法就会执⾏,这就是拦截器的实现原理。

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

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

相关文章

Redis可视化工具:Another Redis Desktop Manager下载安装使用

1.Github下载 github下载地址: Releases qishibo/AnotherRedisDesktopManager GitHub 2. 安装 直接双击exe文件进行安装 3. 连接Redis服务 先启动Redis服务,具体启动过程可参考: Windows安装并启动Redis服务端(zip包&#xff09…

Golang | Leetcode Golang题解之第111题二叉树的最小深度

题目&#xff1a; 题解&#xff1a; func minDepth(root *TreeNode) int {if root nil {return 0}queue : []*TreeNode{}count : []int{}queue append(queue, root)count append(count, 1)for i : 0; i < len(queue); i {node : queue[i]depth : count[i]if node.Left …

FileZilla“服务器发回了不可路由的地址,使用服务器地址代替

问题&#xff1a;在宝塔创建的FTP无法使用&#xff0c;提示“服务器回应不可路由的地址。使用服务器地址代替 第一种解决办法&#xff1a;由于宝塔把FTP被动模式端口范围设置成了39000-40000&#xff0c;所以只需要把阿里云服务器上相应的端口范围开放即可。 第二种解决办法&am…

网络安全之安全协议浅谈

安全协议 安全协议概述安全协议分类IPSecIPSec安全协议IPSec架构IPSec封装模式AH协议ESP协议SET协议SET协议电子交易模型SET协议安全目标认证中心CA 安全协议概述 安全协议是信息交换安全的核心&#xff0c;它在网络不同层次上、针对不同应用&#xff0c;通过对各种密码学技术…

LiveGBS流媒体平台GB/T28181用户手册-云端录像:查看录像、列表视图、时间轴视图、下载、删除

LiveGBS流媒体平台GB/T28181用户手册-云端录像:查看录像、列表视图、时间轴视图、下载、删除 1、云端录像1.1、查看录像1.1.1、时间轴视图1.1.2、列表视图1.1.3、日期切换1.1.4、删除当天 1.2、录像计划1.2.1、录像计划列表1.2.2、编辑录像计划1.2.3、关联通道1.2.4、删除录像计…

解决在cmd里下载的库,但IDLE还是显示不存在的问题

原因一&#xff1a; 环境变量配置 首先&#xff0c;你需要确认你安装库的时候使用的Python环境是否和IDLE使用的Python环境是同一个。如果cmd中你使用的是系统路径下的Python&#xff0c;而IDLE使用的是另一个路径下的Python&#xff0c;那么你在cmd中下载的库&#xff0c;IDL…

存在重复元素 II[简单]

优质博文&#xff1a;IT-BLOG-CN 一、题目 给你一个整数数组nums和一个整数k&#xff0c;判断数组中是否存在两个不同的索引i和j&#xff0c;满足nums[i] nums[j]且abs(i - j) < k。如果存在&#xff0c;返回true&#xff1b;否则&#xff0c;返回false。 示例 1&#…

OpenAI撤回有争议的决定:终止永久性非贬损协议

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

[windows系统安装/重装系统][step-4][番外篇-2]N卡驱动重装 |解决:开机几小时后电脑卡顿 | 后台自动运行了上千个Rundll32进程问题

现象 开机几小时后&#xff0c;电脑变卡&#xff0c;打开后台管理器都卡&#xff0c;后台管理去转圈圈一小会儿后看到后台进程上千个&#xff0c;好多个Rundll32进程 重启下运行会稍快 重启后运行快&#xff0c;后台管理器反应也快 打开后台管理器不卡&#xff08;几小时后打…

力扣刷题---2215. 找出两数组的不同【简单】

题目描述 给你两个下标从 0 开始的整数数组 nums1 和 nums2 &#xff0c;请你返回一个长度为 2 的列表 answer &#xff0c;其中&#xff1a; answer[0] 是 nums1 中所有 不 存在于 nums2 中的 不同 整数组成的列表。 answer[1] 是 nums2 中所有 不 存在于 nums1 中的 不同 整…

营销短信XML接口对接发送示例

在现代社会中&#xff0c;通信技术日新月异&#xff0c;其中&#xff0c;短信作为一种快速、简便的通信方式&#xff0c;仍然在日常生活中占据着重要的地位。为了满足各种应用场景的需求&#xff0c;短信接口应运而生&#xff0c;成为了实现高能有效通信的关键。 短信接口是一种…

L01_JVM 高频知识图谱

这些知识点你都掌握了吗&#xff1f;大家可以对着问题看下自己掌握程度如何&#xff1f;对于没掌握的知识点&#xff0c;大家自行网上搜索&#xff0c;都会有对应答案&#xff0c;本文不做知识点详细说明&#xff0c;只做简要文字或图示引导。 类的生命周期 类加载器 JVM 的内存…

KVM热迁移虚拟机+KSM内存页合并

KVM高级功能部署 文章目录 KVM高级功能部署资源列表基础环境一、静态迁移1.1.在源宿主机上准备虚拟机1.1.1、调试VNC1.1.2、创建虚拟机test011.1.3、console登录test01虚拟机1.1.4、标记虚拟机test01当前IP地址 2.1、提取磁盘和配置文件2.2.1、查看虚拟机test01当前状态2.2.2、…

Gradle的settings.gradle.kts你真的理解吗?

你还在用.gradle文件吗&#xff1f;例如build.gradle、settings.gradle&#xff0c;那么你就out了。现在我们有必要了解一下kts脚本了。在Android项目中&#xff0c;默认有3个文件可以替换成kts脚本&#xff0c;即project的build.gradle、app模块的build.gradle和project的sett…

DDR5—新手入门学习(一)【1-5】

目录 1、DDR背景 &#xff08;1&#xff09;SDR SDRAM时代 &#xff1a; &#xff08;2&#xff09;DDR SDRAM的创新 &#xff1a; &#xff08;3&#xff09;DDR技术的演进 &#xff1a; &#xff08;4&#xff09;需求推动&#xff1a; 2、了解内存 &#xff08;1&…

go学习笔记-从圣经中抄录的接口值的思考

接口值 接口值&#xff0c;由两个部分组成&#xff0c;一个具体的类型和那个类型的值 下面4个语句中&#xff0c;变量w得到了3个不同的值。&#xff08; 开始和最后的值是相同的&#xff09; var w io.Writer w os.Stdout w new(bytes.Buffer) w nil var w io.Writer var…

Python中的数据类型与函数之美

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、Python中的数据类型初探 代码案例&#xff1a; 二、Python内置函数的魅力 代码案例&a…

我用 Midjourney 的这种风格治愈了强迫症

在 Midjourney 能够实现的各种布局之中&#xff0c;有两种风格因其简洁、有序而独居魅力&#xff0c;它们就是平铺 (Flat Lay) 和 Knolling (Knolling 就是 Knolling, 无法翻译&#x1f923;)。要在现实生活中实现这样的美学效果并不容易&#xff0c;你需要精心挑选各种小物件&…

Mac JDK和SDK环境变量配置

一、Java JDK配置 1.下载并安装Java jdk1.8及以上&#xff0c;这个可以在网上自行搜索下载&#xff0c;这里不在详细描述 2.如果不知道JAVA_HOME的安装路径&#xff0c;可以输入命令查看&#xff1a;/usr/libexec/java_home -V &#xff0c;如图 3.在终端输入命令&#xff1…