【Spring Boot】拦截器与统一功能处理
- 博主简介:想进大厂的打工人
- 博主主页:@xyk:
- 所属专栏: JavaEE进阶
上一篇文章我们讲解了Spring AOP是一个基于面向切面编程的框架,用于将某方面具体问题集中处理,通过代理对象来进行传递,但使用原生Spring AOP实现统一的拦截是非常繁琐的。而在本节,我们将使用一种简单的方式进行统一功能处理,具体如下:
- 统一用户登录权限验证
- 统一数据格式返回
- 统一异常处理
目录
文章目录
0、我们先来回顾⼀下最初⽤户登录验证的实现方法:
一、统一用户登录权限验证
1.1 使用原生Spring AOP 实现统一拦截
1.2 使用Spring 拦截器实现统一用户登录验证
1.3 拦截器实现原理
1.4 实现原理源码分析
二、统一异常处理
三、统一数据返回格式
3.1 为什么要统一数据返回格式?
3.2 统一数据返回格式的实现
结语
0、我们先来回顾⼀下最初用户登录验证的实现方法:
@RestController
@RequestMapping("/user")
public class UserController {/*** 某⽅法 1*/@RequestMapping("/m1")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) {
// 有 session 就获取,没有不会创建HttpSession session = request.getSession(false);if (session != null && session.getAttribute("userinfo") != null) {
// 说明已经登录,业务处理return true;} else {
// 未登录return false;}}
// 其他⽅法...
}
从上述代码可以看出,每个⽅法中都有相同的⽤户登录验证权限,它的缺点是:
1. 每个⽅法中都要单独写⽤户登录验证的⽅法,即使封装成公共⽅法,也⼀样要传参调⽤和在⽅法中进⾏判断。
2. 添加控制器越多,调⽤⽤户登录验证的⽅法也越多,这样就增加了后期的修改成本和维护成本。
3. 这些⽤户登录验证的⽅法和接下来要实现的业务⼏何没有任何关联,但每个⽅法中都要写⼀遍。
一、统一用户登录权限验证
1.1 使用原生Spring AOP 实现统一拦截
我们上一篇文章讲解了原生AOP:【Spring】Spring AOP 初识及实现原理解析
以使用原生 Spring AOP 来实现⽤户统⼀登录验证为例, 主要是使用前置通知和环绕通知实现的,具体实现如下:
package com.example.demo.aop;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Component // 随着框架的启动而启动
@Aspect // 告诉框架我是一个切面类
public class UserAspect {// 定义切点(配置拦截规则)@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")public void pointcut(){}@Before("pointcut()")public void beforeAdvice(){System.out.println("执行了前置通知~");}@After("pointcut()")public void AfterAdvice(){System.out.println("执行了后置通知~");}/*** 环绕通知* @param joinPoint* @return*/@Around("pointcut()")public Object aroundAdvice(ProceedingJoinPoint joinPoint){System.out.println("进入了环绕通知~");Object obj = null;try {obj = joinPoint.proceed();} catch (Throwable e) {e.printStackTrace();}System.out.println("退出了环绕通知~");return obj;}}
从上面代码可以看出,使用原生的 Spring AOP 实现统一拦截的难点比较复杂,不易实现。定义拦截规则非常困难。如注册⽅法和登录⽅法是不拦截的,这样的话排除⽅法的规则很难定义,甚⾄没办法定义。而且在切面类中拿到 HttpSession 比较难。
为了解决Spring AOP的问题,Spring 提供了拦截器~
1.2 使用Spring 拦截器实现统一用户登录验证
要使用Spring 拦截器,需要先创建一个实现了 HandlerInterceptor 接口的拦截器类,该接口定义了三个方法:preHandle、postHandle和afterCompletion。
- preHandle方法在请求到达控制器之前执行,可以用于进行身份验证、参数校验等;
- postHandle方法在控制器处理完请求后执行,可以对模型和视图进行操作;
- afterCompletion方法在视图渲染完成后执行,用于清理资源或记录日志。
拦截器的实现分为以下两个步骤:
1. 创建⾃定义拦截器,实现 HandlerInterceptor 接⼝的 preHandle(执⾏具体⽅法之前的预处理)方法。
2. 将⾃定义拦截器加入 WebMvcConfigurer 的 addInterceptors 方法中
具体实现如下:
1.先创建自定义拦截器
package com.example.demo.config;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;public class LoginInterceptor implements HandlerInterceptor {/*** 此方法返回一个 boolean,如果为 true 表示验证成功,可以继续执行后续流程* @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 用户登录业务判断HttpSession session = request.getSession(false);if (session != null && session.getAttribute("userinfo") != null){// 说明用户已经登录return true;}// 可以跳转到登录页面 or 返回一个 401 / 403 没有权限码response.sendRedirect("/login.html");
// response.setStatus(403);return false;}
}
2.配置拦截器并设置拦截规则:
package com.example.demo.config;import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class AppConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**") // 拦截所有请求.excludePathPatterns("/user/login") // 排除的url地址.excludePathPatterns("/user/reg").excludePathPatterns("/**/*.html");}
}
1.3 拦截器实现原理
当有了拦截器后,会在调用 Controller 之前进行相应的业务处理,执行的流程如下图所示:
1.4 实现原理源码分析
所有的 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;Object 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;}// 执⾏ Controller 中的业务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 ⽅法,这样就会我们前⾯定义的拦截器对应上了,如下图所示:
此时,相应的preHandle中的业务逻辑就会执行。
二、统一异常处理
统一异常处理是指在应用程序中定义一个公共的异常处理机制,用来处理所有的异常情况。
本文讲述的统一异常处理使用的是 @ControllerAdvice + @ExceptionHandler 来实现的:
- @ControllerAdvice 表示控制器通知类。
- @ExceptionHandler 异常处理器。
以上两个注解组合使用,表示当出现异常的时候执行某个通知,即执行某个方法事件,具体实现代码如下:
package com.example.demo.config;import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;import java.util.HashMap;/*** @author xyk的电脑* @version 1.0* @description: TODO* @date 2023/7/15 18:49*/@ControllerAdvice
@ResponseBody
public class MyExHandler {/*** 拦截所有的空指针异常,进行统一的数据返回了* @param e* @return*/@ExceptionHandler(NullPointerException.class)public HashMap<String,Object> nullException(NullPointerException e){HashMap<String,Object> result = new HashMap<>();result.put("code",-1);result.put("msg","空指针异常:" + e.getMessage()); // 错误码的描述信息result.put("data",null);return result;}@ExceptionHandler(Exception.class)public HashMap<String,Object> exception(Exception e){HashMap<String,Object> result = new HashMap<>();result.put("code",-1);result.put("msg","异常:" + e.getMessage()); // 错误码的描述信息result.put("data",null);return result;}}
上述代码中,实现了对所有空指针异常和所有异常的保底措施的拦截并进行统一的数据返回。
三、统一数据返回格式
3.1 为什么要统一数据返回格式?
统⼀数据返回格式的优点有很多,⽐如以下几个:
1. ⽅便前端程序员更好的接收和解析后端数据接口返回的数据。
2. 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就⾏了,因为所有接⼝都是这样返回的。
3. 有利于项⽬统⼀数据的维护和修改。
4. 有利于后端技术部⻔的统⼀规范的标准制定,不会出现稀奇古怪的返回内容
3.2 统一数据返回格式的实现
统⼀的数据返回格式可以使⽤ @ControllerAdvice + ResponseBodyAdvice 的⽅式实现,具体实现代码如下:
1.创建一个类,并添加@ControllerAdvice注解
2.实现ResponseBodyAdvice接口,并重写 supports 和 beforeBodyWrite方法
package com.example.demo.config;import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;import java.util.HashMap;/*** @author xyk的电脑* @version 1.0* @description: TODO* @date 2023/7/15 19:18*/@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {@Autowiredprivate ObjectMapper objectMapper;/*** 此方法返回 true 则执行下面 beforeBodyWrite 方法* 反之则不执行*/@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {HashMap<String,Object> result = new HashMap<>();result.put("code",200);result.put("msg","");result.put("data",body);if (body instanceof String){// 需要特殊处理,因为 String 在转换的时候会报错try {return objectMapper.writeValueAsString(result);} catch (JsonProcessingException e) {e.printStackTrace();}}return result;}
}
注意,如果返回的 body 原始数据类型是 String ,则会出现类型转化异常,因为当我们返回给前端Spring引用类型时,Spring的渲染引擎还没有加载好就已经返回给前端了,会出现ClassCastException
。因此,如果原始返回数据类型为 String ,则需要使用 jackson 进行单独处理。
结语
创作不易,如果你有任何问题,欢迎评论区讨论哦~~
相关文章:

【Spring Boot】拦截器与统一功能处理
博主简介:想进大厂的打工人博主主页:xyk:所属专栏: JavaEE进阶 上一篇文章我们讲解了Spring AOP是一个基于面向切面编程的框架,用于将某方面具体问题集中处理,通过代理对象来进行传递,但使用原生Spring AOP实现统一的…...

RabbitMQ的6种工作模式
RabbitMQ的6种工作模式 官方文档: http://www.rabbitmq.com/ https://www.rabbitmq.com/getstarted.html RabbitMQ 常见的 6 种工作模式: 1、simple简单模式 1)、消息产生后将消息放入队列。 2)、消息的消费者监听消息队列,如果队列中…...

MFC第二十六天 CRgn类简介与开发、封装CMemoryDC类并应用开发
文章目录 CRgn类简介与开发CRgn类简介CRgn类区域管理开发CRgn类区域管理与不规则形状的选取 封装CMemoryDC类并应用开发CMemoryDC.h封装CMemoryDC开发游戏透明动画CFlashDlg.hCFlashDlg.cpp 封装CMemoryDC开发游戏动画 附录四大窗口CDC派生类 CRgn类简介与开发 CRgn类简介 CR…...

解决VScode远程服务器时opencv和matplotlib无法直接显示图像的问题
解决VScode远程服务器时opencv和matplotlib无法直接显示图像的问题 1、本方案默认本地已经安装了VScode与MobaXterm2、在服务器端3、在本地端安装MobaXterm4、测试5、opencv显示测试(测试过程中需保持MobaXterm开启的状态)6、 matplotlib显示测试&#x…...

支付模块功能实现(小兔鲜儿)【Vue3】
支付 渲染基础数据 支付页有俩个关键数据,一个是要支付的钱数,一个是倒计时数据(超时不支付商品释放) 准备接口 import request from /utils/httpexport const getOrderAPI (id) > {return request({url: /member/order/$…...
php meilisearch demo
# 创建一个meilisearch 使用完自动销毁 docker run -itd --rm -p 7700:7700 getmeili/meilisearch:v1.3docker-compose 参数 version: "3" networks:flyserver:driver: bridge services:search:image: getmeili/meilisearch:v1.3restart: alwaysenvironment:- MEILI…...

芒格之道——查理·芒格股东会讲话1987-2022
你越是认真生活,你的生活就会越美好! 这里将读书过程划线的内容摘抄在这里,方便自己回顾。 书分为两部分,我先读了后半部分,而且是从后往前读,到了前半部分,我是从前往后读。书还挺贵ÿ…...
如何运营手游联运平台游戏?
运营手游联运平台游戏需要综合考虑多个方面,包括游戏选择、合作伙伴、市场推广、用户运营等。以下是运营手游联运平台游戏的一些建议: 游戏选择:选择优质的手游,确保游戏的品质和内容能够吸引玩家,满足市场需求。 合…...

vscode连接远程Linux服务器
文章目录 一、环境安装1.1 下载vscode1.2 下载vscode-sever 二、ssh链接2.1 安装Remote-SSH2.2 设置vscode ssh2.3 设置免密登录2.3.1 本地生成公私钥2.3.2 服务器端添加公钥 三、安装插件3.1 vscode安装插件3.1.1 在线安装插件3.1.2.1 下载插件3.1.2.2 安装插件 3.2 vscode-se…...

numpy 转换成 cupy 利用GPU执行 错误
ModuleNotFoundError: No module named cupy._core. routines_sorting 提示缺少包 使用 pyinstaller -D views.py --nocons 可以正常打包出来 但是运行出现报错 说明这个打包工具 忽略了很多 隐式导入的包 解决方法很简单 hiddenimports [fastrlock, fastrlock.rlock, cu…...
力扣:55. 跳跃游戏(Python3)
题目: 给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。 数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标。 来源:力扣(LeetCode) 链接:力扣 示例…...

Unity 编辑器资源导入处理函数 OnPreprocessAudio :深入解析与实用案例
Unity 编辑器资源导入处理函数 OnPreprocessAudio 用法 点击封面跳转下载页面 简介 在 Unity 中,资源导入是一个非常重要的环节,它决定了资源在项目中的使用方式和效果。Unity 提供了一系列的资源导入处理函数,其中之一就是 OnPreprocessAud…...

mongodb-win32-x86_64-2008plus-3.4.24-signed.msi
Microsoft Windows [版本 6.1.7601] 版权所有 (c) 2009 Microsoft Corporation。保留所有权利。C:\Users\Administrator>cd C:\MongoDB\Server\3.4\binC:\MongoDB\Server\3.4\bin>C:\MongoDB\Server\3.4\bin>mongod --help Options:General options:-h [ --help ] …...
java的反射
在java语言中,反射机制是指对于处在运行状态的类,都能够获取到这个类的所有属性和方法。对于任意一个对象,都能够调用它的任意一个方法以及访问它的属性;这种通过动态获取类或对象的属性以及方法从而完成调用功能被称为java语言的…...
MySQL — InnoDB 锁
文章目录 锁共享锁和排他锁意向锁记录锁间隙锁临键锁插入意向锁自增锁 锁 加锁是实现数据库并发控制的一个非常重要的技术。当事务在对某个数据对象进行操作前,先向系统发出请求,对其加锁。加锁后事务就对该数据对象有了一定的控制,在该事务…...
首批获得金融级行业云平台认证,天翼云深耕行业云
云计算下半场看什么? 无疑是金融、政务、制造等传统政企用户的上云与用云。随着数字经济发展和产业数字化的提速,上云已是政企用户推动其数字化转型不断深入的重要抓手,成为不可阻挡的趋势。 与互联网用户相比,政企用户上云极为…...
浅谈Python解释器的组成
Python解释器是一个复杂的软件,它可以解释和执行Python代码。以下是Python解释器的主要组成部分: 源代码词法分析器(Lexical Analyzer): 这部分的任务是将输入的Python源代码分解成称为"tokens"的基础元素。例如&#x…...

服务限流治理
一、基础概念 1.什么是服务限流? 限流在日常生活中也很常见,比如节假日你去一个旅游景点,为了不把景点撑爆,管理部门通常会在外面设置拦截,限制景点的进入人数(等有人出来之后,再放新的人进去…...
机器学习笔记 - 使用CLIP在没有数据的情况下创建图像分类器
想象一下,如果我们现在需要对人们是否戴眼镜进行分类,但您没有数据或资源来训练自定义模型。该怎么办?这里我们了解如何使用预先训练的 CLIP 模型来创建自定义分类器,而无需任何培训。这种方法称为零样本图像分类,它可以对原始 CLIP 模型训练期间未明确看到的类别图像进行…...
42.利用 牛顿迭代法解非线性高维方程组(matlab程序)
1.简述 若向量记号为X,方程组就可以写成F(X)0的形式。 我们知道,对于一元函数的牛顿迭代法求根公式 类似的,对于多元函数求根公式 其中X是向量,是非线性方程组对应的雅可比矩阵。 具体求解的时候,我们可以先通过绘图命令绘制图形…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...
JVM垃圾回收机制全解析
Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...

如何在看板中有效管理突发紧急任务
在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...

如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...

springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...

【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)
本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...

招商蛇口 | 执笔CID,启幕低密生活新境
作为中国城市生长的力量,招商蛇口以“美好生活承载者”为使命,深耕全球111座城市,以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子,招商蛇口始终与城市发展同频共振,以建筑诠释对土地与生活的…...