Spring Boot 拦截器实现:登录验证 统一异常处理 返回数据规范化



        学习 Spring 和 servlet 初期,我们在判断用户身份时,都是在每个方法中获取会话、获取对象,这种方式冗余度高,增加代码复杂度,维护成本也高,因此想到可以使用 AOP 来实现一个公共的方法,这个公共的方法专门做判断用户身份,但 AOP 的通知方法是整个横切面都会其进行拦截的,所以对于有的不需要判断用户身份的方法而言,这样处理太过暴力,也不合法,除此之外,使用 AOP 也在获取获取参数上也较为困难,因此使用 Spring Boot 拦截器来实现更加合适。

        下面使用拦截器分别实现(1) 统一登录验证、(2)统一异常处理、(3)统一返回数据格式,这三个实战性的功能。


1. 统一登录验证:

第一步:自定义拦截器

        创建一个普通类,实现 HandlerInterceptor 接口,重写 preHandle 方法,由于拦截器非常常用,所以Spring boot 内置了拦截器的依赖;

  1. preHandle方法:在请求到达Controller之前调用。在拦截器链中的每个拦截器的preHandle方法都会被依次调用。如果某个拦截器的preHandle方法返回false,则后续的拦截器的preHandle方法和Controller方法都不会被执行,请求将被拦截下来。

  2. postHandle方法:在Controller方法执行后,DispatcherServlet渲染视图之前调用。在拦截器链中的每个拦截器的postHandle方法都会被依次调用。可以在这个方法中对ModelAndView进行处理或者添加公共的模型数据。

  3. afterCompletion方法:在DispatcherServlet完成视图渲染之后调用。在拦截器链中的每个拦截器的afterCompletion方法都会被依次调用。可以在这个方法中进行一些资源清理操作,比如释放资源、记录日志等。该方法在整个请求处理流程结束之后被调用。

第二步:注册拦截配置

        写一个普通方法,实现WebMvcConfigurer接口,重写addInterceptor方法:目的是将自定义拦截器配置到系统配置项里,并设置合理的拦截路径。

第三步:添加 Controller 方法 

 执行效果:login不能进入,register可以进入


扩展:添加统一访问前缀:

        假设一种场景,当多个项目有同名的 url 时,在测试抓包等观察过程中如何分别它们各自属于哪个项目呢?

        给请求地址添加一个访问前缀,就可以实现区分了。

 加上前缀后还要记得更改拦截路径:


2. 统一异常处理:

        统一异常处理是指,某些方法可能会触发同一类异常,我们可以借助拦截器去统一拦截获取到,并根据约定返回需要的结果。

第一步:创建异常处理类和方法

        写一个普通类,它内部包含捕获所有异常类的方法,给处理类添加 @ControllerAdvice 注解,给自定义拦截方法添加@ExceptionHandler 注解,注解内传参要传要捕获的异常类对象:

 第二步:编写可能出现异常的方法

有异常检测时,如果出现对应异常,服务器依然按照异常处理方法里的规定给前端返回该返回的东西:

 当没有异常检测时,即使出现对应的异常,服务器的返回和约定无关:

2.1 父子异常:

        上面这种,注解@ExceptionHandler 参数中只有一种异常时,只能捕获处理对应这一种异常,如果想处理很多异常,不用每个都写一个方法,可以在参数中直接传所有异常的父类异常类如:Exception.class

        如果有子异常和父异常的处理方法同时存在时,就先匹配子异常检测方法


3. 统一数据格式返回:

        在日常开发中,后端每个业务给前端返回的数据格式要符合预定格式,但如果有的人忘记了约定格式、或者新人不知道预定格式,可能会按照自己的想法返回数据,这样就会导致业务事故。

        统一数据返回,使用 @ControllerAdvice 注解 + ResponseBodyAdvice 接口实现,我们可以在自定义类中设定好符合约定的返回数据格式,然后拦截每个业务返回的数据,判断它返回的数据格式是否符合约定规范,如果不符合就重新封装数据,返回拦截规则里的数据格式。

        强制性统一数据返回,在返回数据之前进行数据重写

第一步:创建数据处理类

        写一个普通类,实现  ResponseAdvice 接口,重写 supports() 和 beforeBodyWrite() 方法。

  • supports() 方法:该方法相当于一个开关,用来告诉处理方法是否要重写拦截到的内容,返回 true 表示要重写;
  • beforeBodyWrite() 方法:拦截到业务方法要返回的数据,在它返回自己数据前重写错误格式的数据;

 第二步:创建两个可能出现错误格式数据的方法

         getRet1() 方法返回一个 1,getRet2() 方法返回一个 hashMap
        约定正确的格式为一个 hashMap

 总结:

这种自定义拦截数据格式返回存在两个问题:

1.处理方法中设定的返回内容的值是固定写死的;

2.当返回String类型,不能被处理成正确数据;

3.1 扩展:解决转换 String 类型返回值出现错误的问题

        先演示一下,getRer3() 方法,返回的是字符串,理想状态应该是被拦截后被转换成 封装好的 hashMap进行返回,但是没有这样做,因为无法转换,所以出现类转换异常,被之前写的异常处理类捕获到返回异常: HashMap 不能转换为 String

错误原因:

        肯定有疑问:我明明是将 String 转换为 hashMap,为什么报错信息是反过来的?

这里要明白返回的执行流程:

  1. 原方法返回的原 body 是 String 类型;
  2. 统一数据返回之前:将 String 转换为 hashMap;
  3. 浏览器会借助 StringHttpMessageConverter 将 hashMap 转换为 json 字符串;

问题就出在了第三步,浏览器判断时用原body判断是不是 String 类型,是的话就用  StringHttpMessageConverter 将 hashMap 转换为 json,发现不能转换,所以报错。

解决方案1:

在统一数据重写时,单独处理返回值为 String 类型的情况,重写成返回一个 Json 字符串(可以拼接、可以使用jackson),而非 HashMap;

 使用 Jackson 转成 String:

解决方案2:

        既然问题出在了浏览器的转换器上,那我们就想办法不用这个转换器了;

        可以手动将 StringHttpMessageConverter 转换器去掉:

 这样就好了:


4. 拦截器的实现原理:

 图解:

        所有的 Controller 执行都会通过⼀个调度器 DispatcherServlet 来实现

上图解释了拦截器的实现原理:

  1. 请求到达DispatcherServlet,DispatcherServlet是前端控制器,负责接收请求并进行分发。

  2. DispatcherServlet根据配置的拦截器链,依次调用每个拦截器的方法。

  3. 拦截器链中的每个拦截器都实现了 HandlerInterceptor 接口,拦截器的方法会在请求处理的不同阶段被调用。

  4. 在调用每个拦截器的方法之前和之后,会根据返回值来决定是否继续执行下一个拦截器或者Controller方法。

  5. 当所有拦截器的方法都执行完毕后,DispatcherServlet会进行视图渲染,生成响应结果。

通过拦截器的实现,可以在请求处理的不同阶段进行拦截和处理,实现一些通用的功能,比如权限验证、日志记录、异常处理等。拦截器的实现原理基于Java的反射机制和设计模式,通过动态代理生成代理对象,来实现拦截器的调用。

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

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

相关文章

android逆向环境下载记录

frida、frida_tools、obejction、wallbreaker https://github.com/frida/frida/releases pip install frida14.1.2 pip install frida-tools9.0.1 pip install objection1.9.6 https://github.com/hluwa/Wallbreaker objection -g com.hexin.plat.android explore -P ~/.objec…

JAVA基础-基于多线程的聊天程序

引言 什么是程序 ? 一个程序可以有多个进程 。程序是一段静态的代码,它是应用程序执行的蓝本。 什么是进程 ? 一个进程可以有多线程 进程是指一种正在运行的程序,有自己的地址空间。 作为蓝本的程序可以被多次加载到系统的不同内…

智能也是一切社会关系的总和

马克思把人作为“一切社会关系的总和”的论述中,他并非将自然条件作为固定的被给予的条件,而是作为在历史进程中,由于人的活动而发生的改变的被给予的条件来把握的,既从一开始就已经被一定的“生产关系”所塑形和中介了。智能&…

计算机启动过程uefi+gpt方式

启动过程: 一、通电 按下开关,不用多说 二、uefi阶段 通电后,cpu第一条指令是执行uefi固件代码。 uefi固件代码固化在主板上的rom中。 (一)uefi介绍 UEFI,全称Unified Extensible Firmware Interface&am…

Upload-Labs通关

目录 问题 我们首先先来了解一下什么是文件上传 一句话木马 web是用什么语言开发的 最简单的一句话木马 解释 了解完一句话木马 我们了解一下 蚁剑的工作原理 Pass-1 前端验证 1.通过浏览器的插件 关闭这个前端函数 2.通过bp来抓包修改后缀 Pass-2 文件类型的匹配 …

Flutter 状态组件 InheritedWidget

Flutter 状态组件 InheritedWidget 视频 前言 今天会讲下 inheritedWidget 组件,InheritedWidget 是 Flutter 中非常重要和强大的一种 Widget,它可以使 Widget 树中的祖先 Widget 共享数据给它们的后代 Widget,从而简化了状态管理和数据传递…

高数笔记02:导数、微分、中值定理

图源:文心一言 本文是我学习高等数学第二、三章导数、微分、中值定理的一些笔记和心得,希望可以与考研路上的小伙伴一起努力上岸~~🥝🥝 第1版:查资料、画导图、归纳题型~🧩🧩 参考用书1&…

{“msg“:“invalid token“,“code“:401}

项目场景: 提示:这里简述项目相关背景: {“msg“:“invalid token“,“code“:401} 前端请求 后端接口时, 请求失败,控制台出现如下所示报错信息 问题描述 问题: 控制台报错信息如下所示: …

c语言内存函数的深度解析

本章对 memcpy,memmove,memcmp 三个函数进行详解和模拟实现; 本章重点:3个常见内存函数的使用方法及注意事项并学会模拟实现; 如果您觉得文章不错,期待你的一键三连哦,你的鼓励是我创作的动力…

多环境配置及配置文件位置

用端口测试了一下,properties>yml>yaml

Java并发(十三)----共享存在的问题

1、小故事 老王(操作系统)有一个功能强大的算盘(CPU),现在想把它租出去,赚一点外快 小南、小女(不同的线程)来使用这个算盘来进行一些计算,并按照时间给老王支付费用…

neo4j教程-安装部署

neo4j教程-安装部署 Neo4j的关键概念和特点 •Neo4j是一个开源的NoSQL图形存储数据库,可为应用程序提供支持ACID的后端。Neo4j的开发始于2003年,自2007年转变为开源图形数据库模型。程序员使用的是路由器和关系的灵活网络结构,而不是静态表…

【代码随想录 | Leetcode | 第十一天】字符串 | 反转字符串 | 反转字符串 II | 替换空格 | 反转字符串中的单词 | 左旋转字符串

前言 欢迎来到小K的Leetcode|代码随想录|专题化专栏,今天将为大家带来字符串~反转字符串 | 反转字符串 II | 替换空格 | 反转字符串中的单词 | 左旋转字符串的分享✨ 目录 前言344. 反转字符串541. 反转字符串 II剑指 Offer 05. 替换空格151. 反转字符串中的单词剑…

MATLAB与ROS联合仿真——实例程序搭建思路

一、基础运动控制实例程序搭建思路 1、需要完成的任务: (1)通过设定小车运动的速度及转角来控制ROS中小车运动。 (2)通过键盘输入指令控制ROS中小车运动,键盘输入w小车前行,s小车后退&#x…

Windows Server 2012 搭建网关服务器并端口转发

需求 使用 Windows server 作为Hyper-V 虚拟出许多虚拟机,基本上都分配了内网地址,现在需要这些虚拟机访问外网,或者外网直接访问这些虚拟机,必须配置一个网关服务器。我决定直接使用 Windows 的远程访问中的 NAT 服务来完成。 …

【Vue】div标签实现输入框,利用contenteditable=“true“属性的标签实现

推荐个链接&#x1f517;&#xff0c;可以更好的查阅自己遇到的问题&#xff08;点击此处即可跳转&#xff09; 使用 div 实现 input、textarea 输入框 <template><div class"content"><div class"main editTextList" ><divclass&q…

ChatGPT如何帮助学生学习

​ 一些教育工作者担心学生可能使用ChatGPT作弊。因为这个AI工具能写报告和计算机代码&#xff0c;画出复杂图表……甚至已经有许多学校把ChatGPT屏蔽。 研究发现&#xff0c;学生作弊的主要原因是想考得好。是否作弊与作业和考试的打分方式有关&#xff0c;所以这与技术的便…

《零基础入门学习Python》第062讲:论一只爬虫的自我修养10:安装Scrapy

这节课我们来谈谈 Scrapy 说到Python爬虫&#xff0c;大牛们都会不约而同地提起Scrapy。因为Scrapy是一个为了爬取网站数据&#xff0c;提取结构性数据而编写的应用框架。可以应用在包括数据挖掘&#xff0c;信息处理或存储历史数据等一系列的程序中。 Scrapy最初是为了页面抓…

2023年发布的25个开源大型语言模型总结

大型语言模型(llm)是一种人工智能(AI)&#xff0c;在大量文本和代码数据集上进行训练。它们可以用于各种任务&#xff0c;包括生成文本、翻译语言和编写不同类型的创意内容。 今年开始&#xff0c;人们对开源LLM越来越感兴趣。这些模型是在开源许可下发布的&#xff0c;这意味…

HCIA练习2

目录 第一步 启动eNSP&#xff0c;搭建如图所示的拓扑结构 第二步 进行子网的划分 ​第三步 从第二步划分的16个网段中&#xff0c;选择14个网段进行使用 第四步 对路由器各个端口进行IP配置 第五步 对每个路由器的环回接口进行配置 第六步 对路由器进行静态路由配…