每天学习一点点之 Spring Web MVC 之抽象 HandlerInterceptor 实现常用功能(限流、权限等)

背景

这里介绍一下本文的背景(废话,可跳过)。上周有个我们服务的调用方反馈某个接口调用失败率很高,排查了一下,发现是因为这个接口被我之前写的一个限流器给拦截了,随着我们的服务接入了 Sentinel,这个 限流器也可以下线了。于是今天又看了一下当初了实现,发现实现的很粗糙,核心还是基于 Spring AOP 实现的。

又突然想起前段时间由于某些原因想过下掉我们服务中使用的 Shiro,因为只是因为要使用 Shiro 的鉴权( @RequiresPermissions)就要单独引入一个框架,有点重。感觉这种鉴权完全可以自己实现,那怎么实现呢,脑子第一印象又是 Spring AOP。

这里就陷入了一种误区,啥事都用 Spring AOP。Spring AOP 的实现会依赖动态代理,无论是使用 JDK 动态代理还是 CGLIB 动态代理,都会有一定的性能开销。但其实在 Web 端很多功能,都是可以避免使用 Spring AOP 减少无意义的性能损耗,比如上面提到的限流和鉴权

抽象实现

其实原理很简单,就是基于 HandlerInterceptor 来做。但由于类似的功能会很多,比如限流、鉴权、日志打印等,可以将相关功能进行抽象,便于后续类似功能快速实现。

核心抽象类:

package blog.dongguabai.spring.web.mvc.handlerinterceptor.core;import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.util.Objects;/*** @author dongguabai* @date 2023-11-19 23:43*/
public abstract class CustomizedHandlerMethodInterceptor<A extends Annotation> implements HandlerInterceptor {private final Class<A> annotationType;protected CustomizedHandlerMethodInterceptor() {ParameterizedType superclass = (ParameterizedType) getClass().getGenericSuperclass();this.annotationType = (Class<A>) superclass.getActualTypeArguments()[0];}protected abstract boolean preHandle(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, A annotation) throws Exception;protected abstract void afterCompletion(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, A annotation, Exception ex) throws Exception;protected abstract void postHandle(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, ModelAndView modelAndView, A annotation) throws Exception;@Overridepublic final boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (handler instanceof HandlerMethod) {A annotation = getAnnotation((HandlerMethod) handler);if (match(annotation)) {return preHandle(request, response, (HandlerMethod) handler, annotation);}}return true;}@Overridepublic final void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {if (handler instanceof HandlerMethod) {A annotation = getAnnotation((HandlerMethod) handler);if (match(annotation)) {postHandle(request, response, (HandlerMethod) handler, modelAndView, annotation);}}}@Overridepublic final void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {if (handler instanceof HandlerMethod) {A annotation = getAnnotation((HandlerMethod) handler);if (match(annotation)) {afterCompletion(request, response, (HandlerMethod) handler, annotation, ex);}}}protected A getAnnotation(HandlerMethod handlerMethod) {return handlerMethod.getMethodAnnotation(annotationType);}protected boolean match(A annotation) {return Objects.nonNull(annotation);}}

接下来其他的业务功能只需要定义注解后,编写拦截器继承 CustomizedHandlerMethodInterceptor 即可。

业务快速实现:鉴权

定义注解:

package blog.dongguabai.spring.web.mvc.handlerinterceptor.require;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @author Dongguabai* @description* @date 2023-11-19 23:31*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface RequiresPermissions {// PermissionsString[] value();
}

拦截器实现:

package blog.dongguabai.spring.web.mvc.handlerinterceptor.require;import blog.dongguabai.spring.web.mvc.handlerinterceptor.RequestContext;
import blog.dongguabai.spring.web.mvc.handlerinterceptor.core.CustomizedHandlerMethodInterceptor;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;/*** @author dongguabai* @date 2023-11-19 23:34*/
@Component
public class RequiresPermissionsHandlerMethodInterceptor extends CustomizedHandlerMethodInterceptor<RequiresPermissions> {@Overrideprotected boolean preHandle(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, RequiresPermissions annotation) throws Exception {List<String> permissons = Arrays.asList(annotation.value());if (RequestContext.getCurrentUser().getPermissions().stream().anyMatch(permissons::contains)){return true;}System.out.println("无权限.....");return false;}@Overrideprotected void afterCompletion(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, RequiresPermissions annotation, Exception ex) throws Exception {}@Overrideprotected void postHandle(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, ModelAndView modelAndView, RequiresPermissions annotation) throws Exception {}
}

也就是说标注了 RequiresPermissions 注解的接口都会进行鉴权。

验证一下:

package blog.dongguabai.spring.web.mvc.handlerinterceptor;import blog.dongguabai.spring.web.mvc.handlerinterceptor.require.RequiresPermissions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;/*** @author dongguabai* @date 2023-11-20 00:17*/
@RestController
public class TestController {//只有拥有 BOSS 权限的用户才能调用@GetMapping("/get-reports")@RequiresPermissions("BOSS")public String getReports() {return "ALL...";}
}

模拟当前登陆用户(无 BOSS 权限):

package blog.dongguabai.spring.web.mvc.handlerinterceptor;import java.util.Arrays;/*** @author dongguabai* @date 2023-11-20 01:21*/
public final class RequestContext {public static User getCurrentUser() {User user = new User();user.setUsername("tom");user.setPermissions(Arrays.asList("ADMIN", "STUDENT"));return user;}
}

调用:

➜  github curl http://localhost:8080/get-reports
{"message":"无权限..."}%  

即拦截成功。

业务快速实现:限流

定义注解:

package blog.dongguabai.spring.web.mvc.handlerinterceptor.canyon;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;/*** @author dongguabai* @date 2023-11-20 01:56*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface Canyon {double value();long timeout() default 0;TimeUnit timeunit() default TimeUnit.SECONDS;String message() default "系统繁忙,请稍后再试.";
}

实现拦截器:

package blog.dongguabai.spring.web.mvc.handlerinterceptor.canyon;import blog.dongguabai.spring.web.mvc.handlerinterceptor.RequestContext;
import blog.dongguabai.spring.web.mvc.handlerinterceptor.core.CustomizedHandlerMethodInterceptor;
import blog.dongguabai.spring.web.mvc.handlerinterceptor.require.RequiresPermissions;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Map;/*** @author dongguabai* @date 2023-11-19 23:34*/
@Component
public class CanyonHandlerMethodInterceptor extends CustomizedHandlerMethodInterceptor<Canyon> {@Overrideprotected boolean preHandle(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, Canyon annotation) throws Exception {if (tryAcquire()) {return true;}response.setContentType("application/json");response.setCharacterEncoding("UTF-8");response.getWriter().write(String.format("{\"message\":\"%s\"}", annotation.message()));return false;}@Overrideprotected void afterCompletion(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, Canyon annotation, Exception ex) throws Exception {}@Overrideprotected void postHandle(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, ModelAndView modelAndView, Canyon annotation) throws Exception {}/*** todo:流量控制逻辑*/private boolean tryAcquire() {return false;}
}

验证一下:

package blog.dongguabai.spring.web.mvc.handlerinterceptor;import blog.dongguabai.spring.web.mvc.handlerinterceptor.canyon.Canyon;
import blog.dongguabai.spring.web.mvc.handlerinterceptor.require.RequiresPermissions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;/*** @author dongguabai* @date 2023-11-20 00:17*/
@RestController
public class TestController {@GetMapping("/get-reports")@RequiresPermissions("BOSS")public String getReports() {return "ALL...";}@GetMapping("/search")@RequiresPermissions("ADMIN")@Canyon(1)public String search() {return "search...";}
}

调用:

➜  github curl http://localhost:8080/search     
{"message":"系统繁忙,请稍后再试."}%  

即限流成功。

总结

本文首先阐述了虽然 Spring AOP 可以实现限流、鉴权等需要代理的功能,但由于依赖动态代理,会带来一定的性能损耗。然后通过对 HandlerInterceptor 的抽象,我们实现了一套在 Spring Web MVC 层面的静态代理机制,从而方便快速地在 Web 端实现代理功能。

欢迎关注公众号:
在这里插入图片描述

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

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

相关文章

矩阵理论——Gerschgorin定理,以及用python绘制Gerschgorin圆盘动图

矩阵理论——Gerschgorin定理&#xff0c;以及用python绘制Gerschgorin圆盘动图 在矩阵的特征值估计理论当中&#xff0c;有一节是盖尔圆盘定理&#xff1a; 对于一个n阶复数矩阵A&#xff0c;每个特征值lambda位于至少一个Gerschgorin圆盘中&#xff0c;这些圆盘的中心为矩阵…

华为数通HCIP 821BGP 知识点整理

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大三在校生&#xff0c;喜欢AI编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;落798. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc;️…

Canal+Kafka实现MySQL与Redis数据同步(二)

CanalKafka实现MySQL与Redis数据同步&#xff08;二&#xff09; 创建MQ消费者进行同步 在application.yml配置文件加上kafka的配置信息&#xff1a; spring:kafka:# Kafka服务地址bootstrap-servers: 127.0.0.1:9092consumer:# 指定一个默认的组名group-id: consumer-group…

深入解析序列模型:全面阐释 RNN、LSTM 与 Seq2Seq 的秘密

探索序列建模的基础知识和应用。 简介 序列建模是许多领域的一个重要问题&#xff0c;包括自然语言处理 (NLP)、语音识别和语音合成、时间序列预测、音乐生成和「生物信息学」。所有这些任务的共同点是它们需要坚持。接下来的事情的预测是基于历史的。例如&#xff0c;在“哈桑…

Docker入门学习笔记

学习笔记网址推送&#xff1a;wDocker 10分钟快速入门_哔哩哔哩_bilibili docker是用来解决什么问题的&#xff1f; 例如当你在本地主机写了个web应用&#xff0c;而你打算将该应用发送给其他客户端进行案例测试和运行&#xff0c;若是传统做法&#xff0c;就比较复杂&#xf…

conda虚拟环境中安装的cuda和服务器上安装的cuda的异同

服务器上已安装Nvidia提供的cuda&#xff0c;nvcc -V时会出现已安装的CUDA版本。如下图所示&#xff0c;服务器上已安装好的cuda版本为10.1。 但是当我们在Anaconda虚拟环境下安装pytorch或者paddlepaddle等深度学习框架的GPU版本时&#xff0c;通常会选择较高版本的cuda&…

Git 简介及使用(1)

目录 一、在 Linux 环境中安装 Git 1. 先检查当前服务器中是否有 Git&#xff08;如果有显示如下图&#xff09; 2. 安装Git 3. 然后重复第一步&#xff1a;查看 Git 的版本信息即可 二、Git 的初始化及配置 1. 创建目录 2. 对仓库进行初始化 3. 新增两个配置项&#xff08…

PyTorch神经网络-激励函数

在PyTorch 神经网络当中&#xff0c;使用激励函数处理非线性的问题&#xff0c;普通的神经网络出来的数据一般是线性的关系&#xff0c;但是遇到比较复杂的数据的话&#xff0c;需要激励函数处理一些比较难以处理的问题&#xff0c;非线性结果就是其中的情况之一。 FAQ:为什么要…

【Java 进阶篇】Ajax 实现——JQuery 实现方式 `get` 与 `post`

嗨&#xff0c;亲爱的小白们&#xff01;欢迎来到这篇关于使用 jQuery 实现 Ajax 请求的博客。在前端开发中&#xff0c;Ajax 是一项非常重要的技术&#xff0c;它使我们能够在不刷新整个页面的情况下与服务器进行数据交互。而在 jQuery 中&#xff0c;get 和 post 方法提供了简…

全流量分析应用运行和访问情况

在当今数字化时代&#xff0c;应用程序的运行和访问情况对于企业和组织来说至关重要。无论是在线销售平台、移动应用还是企业内部系统&#xff0c;应用的性能和可用性直接影响着用户体验、业务流程以及组织效率。因此&#xff0c;对应用的运行和访问情况进行全面分析和评估&…

JZM-D30室温探针台技术参数

概况&#xff1a; JZM-D30室温探针台的诸多设计都是专用的&#xff0c;探针台的配置主要是根据用户的需求进行选配及设计。例如&#xff0c;要求的磁场型号&#xff0c;电源型号&#xff0c;磁场值&#xff0c;样品台的尺寸等&#xff0c;除此之外&#xff0c;该探针台和我司自…

Go 语言中的map和内存泄漏

map在内存中总是会增长&#xff1b;它不会收缩。因此&#xff0c;如果map导致了一些内存问题&#xff0c;你可以尝试不同的选项&#xff0c;比如强制 Go 重新创建map或使用指针。 在 Go 中使用map时&#xff0c;我们需要了解map增长和收缩的一些重要特性。让我们深入探讨这一点…

架构开发与优化咨询和实施服务

服务概述 得益于硬件平台算力的提升&#xff0c;汽车电子电气架构的集成度逐渐提高&#xff0c;从单体ECU、到功能域集成控制器、到区域集成控制器&#xff0c;多域融合成为了目前行业中软件工程的重要工作内容。同时&#xff0c;在传统控制器C代码开发的基础上&#xff0c;C、…

手把手从零开始训练YOLOv8改进项目(官方ultralytics版本)教程

手把手从零开始训练 YOLOv8 改进项目 (Ultralytics版本) 教程,改进 YOLOv8 算法 本文以Windows服务器为例:从零开始使用Windows训练 YOLOv8 算法项目 《芒果 YOLOv8 目标检测算法 改进》 适用于芒果专栏改进 YOLOv8 算法 文章目录 官方 YOLOv8 算法介绍改进网络代码汇总第…

ROS参数服务器(Param):通信模型、Hello World与拓展

参数服务器在ROS中主要用于实现不同节点之间的数据共享。 参数服务器相当于是独立于所有节点的一个公共容器&#xff0c;可以将数据存储在该容器中&#xff0c;被不同的节点调用&#xff0c;当然不同的节点也可以往其中存储数据。 使用场景一般存储一些机器人的固有参数&…

AIGC 技术在淘淘秀场景的探索与实践

本文介绍了AIGC相关领域的爆发式增长&#xff0c;并探讨了淘宝秀秀(AI买家秀)的设计思路和技术方案。文章涵盖了图像生成、仿真形象生成和换背景方案&#xff0c;以及模型流程串联等关键技术。 文章还介绍了淘淘秀的使用流程和遇到的问题及处理方法。最后&#xff0c;文章展望…

安全项目简介

安全项目 基线检查 密码 复杂度有效期 用户访问和身份验证 禁用administrator禁用guest认证失败锁定 安全防护软件操作系统安全配置 关闭自动播放 文件和目录权限端口限制安全审计… 等保测评 是否举办了安全意识培训是否有应急响应预案有无第一负责人 工作内容 测评准备…

【VRTK】【VR开发】【Unity】7-配置交互能力和向量追踪

【前情提要】 目前为止,我们虽然设定了手模型和动画,还能够正确根据输入触发动作,不过还未能与任何物体互动。要互动,需要给手部设定相应的Interactor能力。 【配置Interactor的抓取功能】 在Hierarchy中选中[VRTK_CAMERA_RIGS_SETUP] ➤ Camera Rigs, Tracked Alias ➤ …

Attingo:西部数据部分SSD存在硬件设计制造缺陷

今年5月&#xff0c;西部数据SanDisk Extreme Pro硬盘陆续有用户反馈有故障发生&#xff0c;用户反馈最多的问题是数据丢失和硬件损坏。8月份&#xff0c;因为这个事情&#xff0c;还被爆出&#xff0c;西部数据面临用户的集体诉讼。 近期&#xff0c;有一个专门从事数据恢复的…

高防CDN的需求分析:社会与企业发展的推动力

在当今数字化飞速发展的时代&#xff0c;网络安全成为社会和企业发展的关键因素之一。随着网络攻击手段的不断升级&#xff0c;企业对于高防CDN&#xff08;内容分发网络&#xff09;的需求逐渐成为保障业务稳健运行的重要部分。从社会和企业发展的角度来看&#xff0c;高防CDN…