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 表示拦截器验证失败,后续的流程和目标方法就不执行。
    @Override
    public 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 afterCompletion
				if (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 ⽅法,这样就会咱们前⾯定义的拦截器对应上了:

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Unity修改Project下的Assets的子文件的图标

Unity修改文件夹的图标 示例: 在右键可以创建指定文件夹。 github链接 https://github.com/SeaeeesSan/SimpleFolderIconCSDN资源的链接 https://download.csdn.net/download/GoodCooking/89347361 去GitHub下载支持原作者哦。重要的事情 截图来自GitHub 。 U…

文件编码格式查看和转换

1、查看文件编码格式 记事本:打开文件后,点击“文件”--“另存为”,可查看文件的编码格式。**Notepad**:打开文件后,即可在右下角查看文件的编码格式。vim:打开文件后,输入“:set fileencoding…

网络安全基础技术扫盲篇 — 名词解释

网络模块基础(网络拓扑图、网络设备、安全设备) 用通俗易懂的话说: 网络拓扑图:它就像一张网络世界的地图,它展现了我们数不清的网站、服务器和设备是如何相互连接的。用简单的话说,它就是给我们指路、告…

人工智能 框架 paddlepaddle 飞桨 使用指南 使用例子 线性回归模型demo 详解

安装过程&使用指南&线性回归模型 使用例子 本来预想 是安装 到 conda 版本的 11.7的 但是电脑没有gpu 所以 安装过程稍有变动,下面简单讲下 conda create -n paddle_env117 python=3.9 由于想安装11.7版本 py 是3.9 所以虚拟环境名称也是 paddle_env117 activa…

C语言 | Leetcode C语言题解之第111题二叉树的最小深度

题目: 题解: typedef struct {int val;struct TreeNode *node;struct queNode *next; } queNode;void init(queNode **p, int val, struct TreeNode *node) {(*p) (queNode *)malloc(sizeof(queNode));(*p)->val val;(*p)->node node;(*p)->…

【pyspark速成专家】7_SparkSQL编程1

目录 一,RDD,DataFrame和DataSet对比 二,创建DataFrame 本节将介绍SparkSQL编程基本概念和基本用法。 不同于RDD编程的命令式编程范式,SparkSQL编程是一种声明式编程范式,我们可以通过SQL语句或者调用DataFrame的相…

编程实现标题栏窗口摇动——显示桌面的未公开细节研究

目录 前言 一、“窗口摇动”功能内部原理 二、explorer.exe 中的 “窗口抖动” 实现 三、“切换到桌面” 功能所扩展的内部细节 四、概念验证 五、进一步研究如何自定义保留窗口列表 原文出处链接:[https://blog.csdn.net/qq_59075481/article/details/139204…

ELK 日志监控平台(二)- 优化日志格式

文章目录 ELK 日志监控平台(二)- 优化日志格式1.日志输出要点2.优化应用的日志格式2.1.确定日志输出要点来源2.1.1.服务名称2.1.2.服务环境2.1.3.日志级别2.1.4.日志输出时间2.1.5.日志内容2.1.6.日志输出对象2.1.7.线程名称 2.2.logback.xml修改日志输出…

win10安装rabbitmq

安装 第一步:下载并安装erlang RabbitMQ服务端代码是使用并发式语言Erlang编写,因此首先需要安装Erlang下载地址:http://www.erlang.org/downloads采用默认安装即可,选择适合的安装路径 添加环境变量 第二步:下载并…

【DevOps】深入了解RabbitMQ:AMQP协议基础、消息队列工作原理和应用场景

目录 一、核心功能 二、优势 三、核心概念 四、工作原理 五、交换机类型 六、消息确认 七、持久性和可靠性 八、插件和扩展 九、集群和镜像队列 十、客户端库 十一、管理界面 十二、应用场景 RabbitMQ是一个基于AMQP协议的消息队列中间件,提供高可用、可…

【数据结构与算法 刷题系列】移除链表元素

💓 博客主页:倔强的石头的CSDN主页 📝Gitee主页:倔强的石头的gitee主页 ⏩ 文章专栏:数据结构与算法刷题系列(C语言) 期待您的关注 目录 一、问题描述 二、解题思路 三、源代码实现 一、问题…

Qt for android 获取USB设备列表(二)JNI方式 获取

简介 基于上篇 [Qt for android 获取USB设备列表(一)Java方式 获取], 这篇就纯粹多了, 直接将上篇代码转换成JNI方式即可。即所有的设备连接与上篇一致。 (https://listentome.blog.csdn.net/article/details/139205850) 关键代码…

UTC与GPS时间转换-[week, sow]

UTC与GPS时间转换-[week, sow] utc2gpsgps2utc测试参考 Ref: Global Positioning System utc2gps matlab源码 function res utc2gps(utc_t, weekStart)%% parameterssec_day 86400;sec_week 604800;leapsec 18; % 默认周一为一周的开始if nargin < 2weekStart d…

HarmonyOS-MPChart绘制一条虚实相接的曲线

本文是基于鸿蒙三方库mpchart&#xff08;OpenHarmony-SIG/ohos-MPChart&#xff09;的使用&#xff0c;自定义绘制方法&#xff0c;绘制一条虚实相接的曲线。 mpchart本身的绘制功能是不支持虚实相接的曲线的&#xff0c;要么完全是实线&#xff0c;要么完全是虚线。那么当我…

VTK 数据处理:特征边提取

VTK 数据处理&#xff1a;特征边提取 VTK 数据处理&#xff1a;特征边提取原理实例 1&#xff1a;边界边提取实例 2&#xff1a;模型特征边提取实例 3&#xff1a;利用 vtkFeatureEdges 提取的边界补洞实例 4&#xff1a;利用 vtkFillHolesFilter 补洞 VTK 数据处理&#xff1a…

[C语言]自定义类型详解:结构体、联合体、枚举

目录 &#x1f680;结构体 &#x1f525;结构体类型的声明 &#x1f525;结构的自引用 &#x1f525;结构体变量的定义和初始化 &#x1f525;结构体内存对齐 &#x1f525;结构体传参 &#x1f525;结构体实现位段&#xff08;位段的填充&可移植性&#xff09; &a…

企业异地网络组网:SD-WAN解决方案的优势

在当今全球化的商业环境中&#xff0c;企业的业务扩展已不再局限于本地或单一国家。随着分公司和子公司的不断增加&#xff0c;企业总部与这些分支机构之间的数据通信和资源共享变得尤为重要。然而&#xff0c;传统的网络访问方式&#xff0c;如点对点电路和多协议标签交换&…

17.7K星开源产品分析平台:Posthog

Posthog&#xff1a;开源洞察&#xff0c;产品优化的得力助手 - 精选真开源&#xff0c;释放新价值。 概览 PostHog是一个全面开源的平台&#xff0c;旨在帮助团队构建更好的产品。它提供了从产品分析到会话回放、功能标志和A/B测试等一系列工具&#xff0c;支持自托管&#x…

个人博客网站搭建笔记1

文章目录 前言要求自己的理解资源过程视频教程SpringBoot开发一个小而美的个人博客p1课程介绍p2需求和功能 前言 自己之前其实就想搭建一个属于自己的网站&#xff0c;但是不知道怎么操作&#xff0c;没找到合适的教程&#xff0c;&#xff08;手把手的那种&#xff09;&#…

Prometheus+Grafana监控服务器、mysql数据库并配置报警规则推送邮箱

文章目录 一、安装prometheus1.1下载1.2 安装1.3 开机启动1.4 验证 二、安装 Grafana2.1 下载2.2 安装2.3 启动2.4 验证 三、安装服务器监控 node_exporter3.1 下载3.2 安装3.3 设置 node_exporter 系统服务3.4 设置开机自动启动3.5 验证3.6配置Prometheus3.7 修改 Prometheus …