Spring Boot统一处理功能——拦截器

1.用户登录权限校验

⽤户登录权限的发展从之前每个⽅法中⾃⼰验证⽤户登录权限,到现在统⼀的⽤户登录验证处理,它是⼀个逐渐完善和逐渐优化的过程。

1.1最初用户登录

我们先来回顾⼀下最初⽤户登录验证的实现⽅法:
@RestController
@RequestMapping("/user2")
public class User2Controller {/*** * 某方法1*/@RequestMapping("/m")public Object method(HttpServletRequest request){//有session就获取,没有不会创建HttpSession session=request.getSession(false);if(session!=null&&session.getAttribute("userinfo")!=null){//说明已经登录,业务处理return true;}else {return false;}}/***某方法2*/@RequestMapping("/m2")public Object method2(HttpServletRequest request){HttpSession session=request.getSession(false);if(session!=null&&session.getAttribute("userinfo")!=null){return true;}else {return false;}}}
从上述代码可以看出,每个⽅法中都有相同的⽤户登录验证权限,它的缺点是:
1. 每个⽅法中都要单独写⽤户登录验证的⽅法,即使封装成公共⽅法,也⼀样要传参调⽤和在⽅法中进⾏判断。
2. 添加控制器越多,调⽤⽤户登录验证的⽅法也越多,这样就增加了后期的修改成本和维护成本。
3. 这些⽤户登录验证的⽅法和接下来要实现的业务⼏何没有任何关联,但每个⽅法中都要写⼀遍。
所以提供⼀个公共的 AOP ⽅法来进⾏统⼀的⽤户登录权限验证迫在眉睫。

2Spring AOP用户统一登录验证的问题

说到统⼀的⽤户登录验证,我们想到的第⼀个实现⽅案是 Spring AOP 前置通知或环绕通知来实现,具体实现代码如下:
@Slf4j
@Aspect//定义了一个切点
@Component  //演示统一功能,先去掉
public class LoginAspect {@Around("pointcut()")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {Object oj=null;long start=System.currentTimeMillis();//调用目标方法try {//执行连接点的方法oj = joinPoint.proceed();} catch (Throwable e) {throw e;}log.info(joinPoint.getSignature().toString()+"耗时:"(System.currentTimeMillis()-start));//必须要写返回结果return oj;}@Before("execution(* com.example.springaopdemo.controller.TestController.* (..))")public void before2(){log.info("before");}
}
如果要在以上 Spring AOP 的切⾯中实现⽤户登录权限效验的功能,有以下两个问题:
1. 没办法获取到 HttpSession 对象。
2. 我们要对⼀部分⽅法进⾏拦截,⽽另⼀部分⽅法不拦截,如注册⽅法和登录⽅法是不拦截的,这样的话排除⽅法的规则很难定义,甚⾄没办法定义。
那这样如何解决呢?

3Spring拦截器

对于以上问题 Spring 中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现分为以下两个步骤:
1. 创建⾃定义拦截器,实现 HandlerInterceptor 接⼝的 preHandle(执⾏具体⽅法之前的预处理)⽅法。
2. 将⾃定义拦截器加⼊ WebMvcConfigurer 的 addInterceptors ⽅法中。
具体实现如下。

3.1自定义拦截器

接下来使⽤代码来实现⼀个⽤户登录的权限效验,⾃定义拦截器是⼀个普通类,具体实现代码如下:
/*** 创建一个拦截器*/
@Componentpublic class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//判断是否登录HttpSession session=request.getSession(false);if(session!=null&&session.getAttribute("username")!=null){//通过,不进行处理return true;}//表示没有权限response.setStatus(401);return false;}
}

3.2将自定义拦截器加入到系统配置

将上⼀步中的⾃定义拦截器加⼊到系统配置信息中,具体实现代码如下:
@Configuration
public class AppConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;//添加拦截器@Overridepublic void addInterceptors(InterceptorRegistry registry) {//添加拦截器registry.addInterceptor(loginInterceptor)//拦截哪些url.addPathPatterns("/**")//放过哪些url.excludePathPatterns("/api/user/login").excludePathPatterns("/api/user/reg");}// 所有的接⼝添加 api 前缀@Overridepublic void configurePathMatch(PathMatchConfigurer configurer) {configurer.addPathPrefix("api", c -> true);}}
其中:
●addPathPatterns:表示需要拦截的 URL,“**”表示拦截任意⽅法(也就是所有⽅法)。
●excludePathPatterns:表示需要排除的 URL。
说明:以上拦截规则可以拦截此项⽬中的使⽤ URL,包括静态⽂件(图⽚⽂件、JS 和 CSS 等⽂件)
排除所有的静态资源
// 拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**") // 拦截所有接⼝.excludePathPatterns("/**/*.js").excludePathPatterns("/**/*.css").excludePathPatterns("/**/*.jpg").excludePathPatterns("/login.html").excludePathPatterns("/**/login"); // 排除接⼝
}

登录拦截器:

@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {@RequestMapping("/get")//获取用户信息public String getInfo(){log.info("get info");return "get info";}//注册@RequestMapping("/reg")public String reg(){log.info("reg");
//        int a=10/0;return "reg";}@RequestMapping("/login")public boolean login(HttpServletRequest request,String username,String password){log.info("login");//判断username和password是否为空
//        if(username!=null && "".equals(username)&& password!=null &&"".equals(password)){
//
//        }//Springif(!StringUtils.hasLength(username)||!StringUtils.hasLength(password)){return false;}if(!"admin".equals(username) || !"admin".equals(password)) {return true;}HttpSession session = request.getSession(true);session.setAttribute("username", username);return true;}}

直接访问get会被拦截,表示拦截器创建成功

4拦截器实现原理

正常情况下的调⽤顺序:

 

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

 

4.1 实现原理源码分析

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

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

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {try {ModelAndView mv = null;Exception dispatchException = null;try {processedRequest = this.checkMultipart(request);multipartRequestParsed = processedRequest != request;mappedHandler = this.getHandler(processedRequest);if (mappedHandler == null) {this.noHandlerFound(processedRequest, response);return;}HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());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;}// 执行Contorller层的逻辑mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}this.applyDefaultViewName(processedRequest, mv);mappedHandler.applyPostHandle(processedRequest, response, mv);} catch (Exception var20) {dispatchException = var20;} catch (Throwable var21) {dispatchException = new NestedServletException("Handler dispatch failed", var21);}this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);} catch (Exception var22) {this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);} catch (Throwable var23) {this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));}} finally {if (asyncManager.isConcurrentHandlingStarted()) {if (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}} else if (multipartRequestParsed) {this.cleanupMultipart(processedRequest);}}}
从上述源码可以看出在开始执⾏ Controller 之前,会先调⽤ 预处理⽅法 applyPreHandle,⽽
applyPreHandle ⽅法的实现源码如下:
    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {// 获取项目中使用的拦截器, 我们之前就是实现了这个类,然后添加的拦截器// 在这里,我们自定义的拦截器就会被调用,用户登陆权限验证的方法就会实现,这就是拦截器实现的原理HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);if (!interceptor.preHandle(request, response, this.handler)) {this.triggerAfterCompletion(request, response, (Exception)null);return false;}}return true;}
从上述源码可以看出,在 applyPreHandle 中会获取所有的拦截器 HandlerInterceptor 并执⾏拦截器中的 preHandle ⽅法,这样就会咱们前⾯定义的拦截器对应上了,如下图所示:

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

 4.2拦截器小结

通过上⾯的源码分析,我们可以看出,Spring 中的拦截器也是通过动态代理和环绕通知的思想实现的,⼤体的调⽤流程如下:

5拓展:统一前缀访问

所有请求地址添加 api 前缀: 

    // 所有的接⼝添加 api 前缀@Overridepublic void configurePathMatch(PathMatchConfigurer configurer) {configurer.addPathPrefix("api", c -> true);}
其中第⼆个参数是⼀个表达式,设置为 true 表示启动前缀。
我们在访问这个程序时,就要加上api前缀才能访问到,否则会被拦截:

 

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

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

相关文章

W6100-EVB-PICO作为TCP Client 进行数据回环测试(五)

前言 上一章我们用W6100-EVB-PICO开发板通过DNS解析www.baidu.com&#xff08;百度域名&#xff09;成功得到其IP地址&#xff0c;那么本章我们将用我们的开发板作为客户端去连接服务器&#xff0c;并做数据回环测试&#xff1a;收到服务器发送的数据&#xff0c;并回传给服务器…

JavaWeb学习|JSP相关内容

1.什么是JSP Java Server Pages: Java服务器端页面&#xff0c;也和Servlet一样&#xff0c;用于动态Web技术! 最大的特点: 。写JSP就像在写HTML 。区别: 。HTML只给用户提供静态的数据 。JSP页面中可以嵌入JAVA代码&#xff0c;为用户提供动态数据 JSP最终也会被转换成为一…

【沁恒蓝牙mesh】CH58x flash分区之利用随机数作为蓝牙mesh地址

本文主要介绍了 沁恒蓝牙芯片 CH58x 的flash 分区与数据存储管理&#xff0c;利用随机数作为蓝牙mesh地址&#xff0c;蓝牙mesh采用自组网 &#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是喜欢记录零碎知识点的小菜鸟。&#x1f60e;&#…

2023年华数杯数学建模C题思路 - 母亲身心健康对婴儿成长的影响

# 1 赛题 C 题 母亲身心健康对婴儿成长的影响 母亲是婴儿生命中最重要的人之一&#xff0c;她不仅为婴儿提供营养物质和身体保护&#xff0c; 还为婴儿提供情感支持和安全感。母亲心理健康状态的不良状况&#xff0c;如抑郁、焦虑、 压力等&#xff0c;可能会对婴儿的认知、情…

ZMQ发布订阅模式二次封装

ZeroMQ 参考ZMQ从入门到掌握一 ZeroMQ是一种基于消息队列的多线程网络库&#xff0c;其对套接字类型、连接处理、帧、甚至路由的底层细节进行抽象&#xff0c;提供跨越多种传输协议的套接字。ZeroMQ 并不是一个对 socket 的封装&#xff0c;不能用它去实现已有的网络协议。它有…

《论文阅读》通过生成会话模型的迁移学习会话中的情感识别

《论文阅读》通过生成会话模型的迁移学习会话中的情感识别 前言简介模型结构Source TaskTarget Task损失函数前言 你是否也对于理解论文存在困惑? 你是否也像我之前搜索论文解读,得到只是中文翻译的解读后感到失望? 小白如何从零读懂论文?和我一起来探索吧! 今天为大家…

Qt6之QListWidget——Qt仿ToDesk侧边栏(1)

一、 QLitWidget概述 注意&#xff1a;本文不是简单翻译Qt文档或者接口函数&#xff0c;而侧重于无代码Qt设计器下演示使用。 QListWidget也称列表框类&#xff0c;它提供了一个类似于QListView提供的列表视图&#xff0c;但是它具有一个用于添加和删除项的经典的基于项的接口…

JavaScript算法【入门】

作者&#xff1a;20岁爱吃必胜客&#xff08;坤制作人&#xff09;&#xff0c;近十年开发经验, 跨域学习者&#xff0c;目前于海外某世界知名高校就读计算机相关专业。荣誉&#xff1a;阿里云博客专家认证、腾讯开发者社区优质创作者&#xff0c;在CTF省赛校赛多次取得好成绩。…

Jmeter(四) - 从入门到精通 - 创建网络测试计划(详解教程)

1.简介 在本节中&#xff0c;您将学习如何创建基本的 测试计划来测试网站。您将创建五个用户&#xff0c;这些用户将请求发送到JMeter网站上的两个页面。另外&#xff0c;您将告诉用户两次运行测试。因此&#xff0c;请求总数为&#xff08;5个用户&#xff09;x&#xff08;2…

[保研/考研机试] 约瑟夫问题No.2 C++实现

题目要求&#xff1a; 输入、输出样例&#xff1a; 源代码&#xff1a; #include<iostream> #include<queue> #include<vector> using namespace std;//例题5.2 约瑟夫问题No.2 int main() {int n, p, m;while (cin >> n >> p >> m) {//如…

Whisper.cpp 编译使用

Whisper.cpp 编译使用 whisper.cpp 是牛人 ggerganov 对 openai 的 whisper 语音识别模型用 C 重新实现的项目&#xff0c;开源在 github 上&#xff0c;具有轻量、性能高&#xff0c;实用性强等特点。这篇文章主要记录在 windows 平台&#xff0c;如何使用该模型在本地端进行…

docker安装MinIO

简介 Minio 是一个面向对象的简单高性能存储服务。使用 Go 语言编写&#xff0c;性能高、具有跨平台性。 Minio 官网为&#xff1a;https://min.io &#xff0c;有一个中文站点&#xff0c;单内容更新不是很及时&#xff0c;建议从原始官网学习。 本文采用 Docker 安装&…

vscode自动添加注释说明

1. 安装vscode 双击安装程序,默认安装即可(如:VSCodeSetup-x64-1.70.2.exe) 2. 安装doxygen文档生成插件 1> 打开vscode软件,点击左侧插件管理菜单 2> 点击右上角’…‘按钮,选择’Install from VSIX’(联网状态可以直接搜索doxygen下载安装) 3> 选择doxygen离线安装…

【深度学习】【风格迁移】Zero-shot Image-to-Image Translation

论文&#xff1a;https://arxiv.org/abs/2302.03027 代码&#xff1a;https://github.com/pix2pixzero/pix2pix-zero/tree/main 文章目录 Abstract1. Introduction相关工作3. Method Abstract 大规模文本到图像生成模型展示了它们合成多样且高质量图像的显著能力。然而&#x…

Linux项目部署

目录 一JAVAWeb环境的部署【安装JDK&#xff0c;MySQL数据库&#xff0c;Tomcat】 二.手工部署SpringBoot项目&#xff08;写的最好的&#xff09; 1.在IDEA中开发SpringBoot项目并打成jar包--点击右侧的Maven执行package命令 2.将jar包上传到Linux服务器 3.执行以下命令&a…

mysql的高级查询语句

目录 一、本文前言 二、高效查询方式 1&#xff09;指定指字段进行查看 2&#xff09;对字段进行去重查看 3&#xff09;where条件查询 4&#xff09;and 和 or 进行逻辑关系的增加 5&#xff09;查询取值列表中的数据 6&#xff09;between的引用 7&#xff09;like…

NAT及其实验(eNSP,细致易懂)

目录 NAT产生背景 NAT概述NAT&#xff08;Network Address Translation&#xff09;&#xff0c;网络地址转换 NAT工作规则 标准NAT技术 NAPT[网络地址端口转换[Port-->传输层-端口编号]] Easy IP——最简单的PAT NAT Server 静态NAT实验 动态NAT实验 NAPT实验 N…

Redis基础 (三十八)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、概述 1.1 NoSQL 1.2 Redis 二、安装 2.1 安装方式 &#xff1a; 三、目录结构 3.1 rpm -ql redis 3.2 /etc/redis.conf 主配置文件 3.3 /var/lib/redis …

【BMC】OpenBMC开发基础2:修改原有程序

修改原有程序 通常情况下我们会需要修改OpenBMC原有的程序来适配我们的项目&#xff0c;本节将介绍一般的流程。 为此首先我们需要了解devtool这个工具&#xff0c;注意它不是前端开发用的那个devtool&#xff0c;而是由OE&#xff08;或者Yocto&#xff1f;&#xff09;提供…

Android 实现 RecyclerView下拉刷新,SwipeRefreshLayout上拉加载

上拉、下拉的效果图如下&#xff1a; 使用步骤 1、在清单文件中添加依赖 implementation ‘com.android.support:recyclerview-v7:27.1.1’ implementation “androidx.swiperefreshlayout:swiperefreshlayout:1.0.0” 2、main布局 <LinearLayout xmlns:android"http…