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

news2024/11/15 8:43:46

之前的博客中讲解了关于 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 ⽅法,这样就会咱们前⾯定义的拦截器对应上了:

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

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

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

相关文章

kali基本扫描工具(自带)

免责声明:本文仅做技术交流与学习...请勿非法破坏... 详细用法: 命令 -h/百度/翻译 fping 用法 hostlist 文件里面为ip fping -a -q -f hostlist -a 只看存活的 fping -g 202.100.1.1 202.100.1.255 -a -q > Ahost 输出到Ahost文件上 nping nping -c 1 201.100.2.155-244 …

动态规划之背包问题中如何确定遍历顺序的问题-组合or排列?

关于如何确定遍历顺序 322. 零钱兑换中,本题求钱币最小个数,那么钱币有顺序和没有顺序都可以,都不影响钱币的最小个数。 所以本题并不强调集合是组合还是排列。 如果求组合数就是外层for循环遍历物品,内层for遍历背包。 如果求…

【数据结构】二叉树的认识与实现

目录 二叉树的概念: 二叉树的应用与实现: 二叉树实现接口: 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树 二叉树节点个数​编辑 二叉树叶子节点个数 二叉树第k层节点个数 二叉树查找值为x的节点​编辑 二叉树前序遍…

Day 3:1738. 找出第 K 大的异或坐标值

Leetcode 1738. 找出第 K 大的异或坐标值 给你一个二维矩阵 matrix 和一个整数 k &#xff0c;矩阵大小为 m x n 由非负整数组成。 矩阵中坐标 (a, b) 的 值 可由对所有满足 0 < i < a < m 且 0 < j < b < n 的元素 matrix[i][j]&#xff08;下标从 0 开始计…

【Linux】进程通信实战 —— 进程池项目

送给大家一句话: 没有一颗星&#xff0c;会因为追求梦想而受伤&#xff0c;当你真心渴望某样东西时&#xff0c;整个宇宙都会来帮忙。 – 保罗・戈埃罗 《牧羊少年奇幻之旅》 &#x1f3d5;️&#x1f3d5;️&#x1f3d5;️&#x1f3d5;️&#x1f3d5;️&#x1f3d5;️ &a…

# linux 系统 没有 ifconfig 命令,提示: ifconfig: command not found

sudo ip route add default via 192.168.1.1 dev eth0# linux 系统 没有 ifconfig 命令&#xff0c;提示&#xff1a; ifconfig: command not found 一、问题描述&#xff1a; 有些伙伴在学习 linux 系统时&#xff0c;在 使用 ifconfig 命令 查询 系统 IP 出现 ifconfig: co…

【LakeHouse】Apache Iceberg + Amoro 助力网易构建云原生湖仓

Apache Iceberg Amoro 助力网易构建云原生湖仓 1.云原生湖仓背景与挑战2.Apache Iceberg 、Amoro 与云原生2.1 Apache Iceberg2.2 Amoro 简介 3.Apache Iceberg Amoro 云原生实践3.1 云上湖仓案例一3.2 云上湖仓案例二3.3 云上湖仓案例三 4.Amoro 未来发展规划 出品社区&…

上百个神奇有趣的小工具,总有一款适合你

这几天了不起在逛 GitHub 时&#xff0c;发现了一个有趣的项目——MikuTools。这是一个工具集合的 web 项目&#xff0c;提供了上百个有趣实用神奇的工具。 项目简介 MikuTools 项目主页的介绍很简单&#xff1a;一个轻量的工具集合。确实&#xff0c;项目界面非常简洁&#…

免费图片文字转换成文本,ocr文字识别软件免费版,真的太实用了!

截屏短视频上一段扎心文字&#xff0c;想把它发到朋友圈怎么办&#xff1f;这时候就需要一个OCR识别软件。 它就像一个聪明的小助手&#xff0c;它可以帮助电脑“看懂”书本上或者图片里的字。就像我们用眼睛看字一样&#xff0c;OCR软件用它的“眼睛”扫描图片&#xff0c;识…

企业活动想找媒体报道宣传怎样联系媒体?

在那遥远的公关江湖里,有一个传说,说的是一位勇士,手持鼠标和键盘,踏上了寻找媒体圣杯的征途。这位勇士,就是我们亲爱的市场部门小李,他的任务是为公司即将举行的一场盛大的企业活动找到媒体的聚光灯。 小李的故事,开始于一张空白的Excel表格,上面列着各大媒体的名称,旁边是一片…

面试题·栈和队列的相互实现·详解

A. 用队列实现栈 用队列实现栈 实现代码如下 看着是队列&#xff0c;其实实际实现更接近数组模拟 typedef struct {int* queue1; // 第一个队列int* queue2; // 第二个队列int size; // 栈的大小int front1, rear1, front2, rear2; // 两个队列的首尾指针 } MyS…

gfast:基于全新Go Frame 2.3+Vue3+Element Plus构建的全栈前后端分离管理系统

gfast&#xff1a;基于全新Go Frame 2.3Vue3Element Plus构建的全栈前后端分离管理系统 随着信息技术的飞速发展和数字化转型的深入&#xff0c;后台管理系统在企业信息化建设中扮演着越来越重要的角色。为了满足市场对于高效、灵活、安全后台管理系统的需求&#xff0c;gfast应…

ICQ 将于 6 月关闭,这是一种奇怪的方式,发现它在 2024 年仍然活跃

你知道ICQ还活着吗&#xff1f;不过&#xff0c;不要太兴奋;它将永远消失。 还记得ICQ吗&#xff1f;如果你这样做了&#xff0c;你可能会记得它是AOL在1998年购买的Messenger客户端&#xff0c;就在Yahoo Instant Messager和MSN Messenger加入竞争的时候。然后Skype出现了&…

网络模型-BFD与网络协议联动

一、BFD:双向转发检测 双向转发检测BFD(Bidirectional Forwarding Detection)是一种全网统一的检测机制&#xff0c;用于快速检测、监控网络中链路或者IP路由的转发连通状况。 1、BFD优点: 对相邻转发引擎之间的通道提供轻负荷、快速故障检测。这些故障包括接口数据链路&#…

g-h Filter 详细讲解

g-h 过滤器 g-h 滤波器百科介绍。 之前的翻译大家&#xff0c;我看都没什么阅读量&#xff0c;可能大家都不是很想看&#xff08;估计也是我英文太水&#xff09;。那么这篇博客我就先暂停直接翻译原文&#xff0c;而是直接说一下自己的理解。 本文章背后的书的详细介绍可以…

单片机原理及技术(二)—— AT89S51单片机(一)(C51编程)

目录 一、AT89S51单片机的片内硬件结构 二、AT89S51的引脚功能 2.1 电源及时钟引脚 2.2 控制引脚 2.3 并行 I/O口引脚 三、AT89S51的CPU 3.1 运算器 3.1.1 算术逻辑单元&#xff08;ALU&#xff09; 3.1.2 累加器A 3.1.3 程序状态字寄存器&#xff08;PSW&#xff09…

Linux-应用编程学习笔记(三、文件属性和目录)

一、文件类型 1、普通文件&#xff08;ls -l 文件&#xff0c;权限前边第一个"-"代表普通文件&#xff1b;stat 文件&#xff09; 文本文件&#xff1a;ASCII字符 二进制文件&#xff1a;数字0/1 2、目录文件&#xff08;‘’d&#xff09;&#xff1a;文件夹 3…

声压级越大,STIPA 越好,公共广播就越清晰吗?

在公共广播中&#xff0c;有些朋友经常问到是不是声压越大&#xff0c;广播清晰度就越高&#xff0c;下面我从搜集了一些专业技术资料&#xff0c;供大家参考。 一、声压级越大&#xff0c;STIPA 越好吗&#xff1f; 不完全是。最初&#xff0c;人们认为当声压级达到 60 dBA 以…

STL源码刨析:序列式容器之vector

目录 1.序列式容器和关联式容器 2.vector的定义和结构 3.vector的构造函数和析构函数的实现 4.vector的数据结构以及实现源码 5.vector的元素操作 前言 本系列将重点对STL中的容器进行讲解&#xff0c;而在容器的分类中&#xff0c;我们将容器分为序列式容器和关联式容器。本章…

氢燃料电池汽车行业发展

文章目录 前言 市场分布 整车销售 发动机配套 氢气供应 发展动能 参考文献 前言 见《氢燃料电池技术综述》 见《燃料电池工作原理详解》 见《燃料电池发电系统详解》 见《燃料电池电动汽车详解》 市场分布 纵观全球的燃料电池汽车市场&#xff0c;截至2022年底&#xff…