每天学习一点点之 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.rhkb.cn/news/199054.html

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

相关文章

MTK Pump Express 快速充电原理分析

1 MTK PE 1.1 原理 在讲正文之前&#xff0c;我们先看一个例子。 对于一块电池&#xff0c;我们假设它的容量是6000mAh&#xff0c;并且标称电压是3.7V&#xff0c;换算成Wh(瓦时)为单位的值是22.3Wh(6000mAh*3.7V)&#xff1b;普通的充电器输出电压电流是5V2A(10W)&#xff0c…

RK3588平台开发系列讲解(项目篇)嵌入式AI的学习步骤

文章目录 一、嵌入式AI的学习步骤1.1、入门Linux1.2、入门AI 二、瑞芯微嵌入式AI2.1、瑞芯微的嵌入式AI关键词2.2、AI模型部署流程 沉淀、分享、成长&#xff0c;让自己和他人都能有所收获&#xff01;&#x1f604; &#x1f4e2; 本篇将给大家介绍什么是嵌入式AI。 一、嵌入…

Docker与Kubernetes结合的难题与技术解决方案

文章目录 1. **版本兼容性**技术解决方案 2. **网络通信**技术解决方案 3. **存储卷的管理**技术解决方案 4. **安全性**技术解决方案 5. **监控和日志**技术解决方案 6. **扩展性与自动化**技术解决方案 7. **多集群管理**技术解决方案 结语 &#x1f388;个人主页&#xff1a…

编程刷题网站以及实用型网站推荐

1、牛客网在线编程 牛客网在线编程https://www.nowcoder.com/exam/oj?page1&tab%E8%AF%AD%E6%B3%95%E7%AF%87&topicId220 2、力扣 力扣https://leetcode.cn/problemset/all/ 3、练码 练码https://www.lintcode.com/ 4、PTA | 程序设计类实验辅助教学平台 PTA | 程…

【Java 进阶篇】Ajax 实现——原生JS方式

大家好&#xff0c;欢迎来到这篇关于原生 JavaScript 中使用 Ajax 实现的博客&#xff01;在前端开发中&#xff0c;我们经常需要与服务器进行数据交互&#xff0c;而 Ajax&#xff08;Asynchronous JavaScript and XML&#xff09;是一种用于创建异步请求的技术&#xff0c;它…

云原生专栏丨基于服务网格的企业级灰度发布技术

灰度发布&#xff08;又名金丝雀发布&#xff09;是指在黑与白之间&#xff0c;能够平滑过渡的一种发布方式。在其上可以进行A/B testing&#xff0c;即让一部分用户继续用产品特性A&#xff0c;一部分用户开始用产品特性B&#xff0c;如果用户对B没有什么反对意见&#xff0c;…

广州华锐互动VRAR:VR教学楼地震模拟体验增强学生防震减灾意识

在当今社会&#xff0c;地震作为一种自然灾害&#xff0c;给人们的生活带来了巨大的威胁。特别是在学校这样的集体场所&#xff0c;一旦发生地震&#xff0c;后果将不堪设想。因此&#xff0c;加强校园安全教育&#xff0c;提高师生的防震减灾意识和能力&#xff0c;已经成为了…

springboot中动态api如何设置

1.不需要编写controller 等mvc层&#xff0c;通过接口动态生成api。 这个问题&#xff0c;其实很好解决&#xff0c;以前编写接口&#xff0c;是要写controller&#xff0c;需要有 RestController RequestMapping("/test1") public class xxxController{ ApiOperat…

Zotero在word中插入带超链接的参考文献/交叉引用/跳转参考文献

Zotero以其丰富的插件而闻名&#xff0c;使用起来十分的带劲&#xff0c;最重要的是它是免费的、不卡顿&#xff0c;不像某专业软件。 然而Zotero在word插入参考文献时&#xff0c;无法为参考文献添加超链接&#xff0c;这是一个不得不提的遗憾。 不过&#xff0c;有大佬已经…

asp.net健身会所管理系统sqlserver

asp.net健身会所管理系统sqlserver说明文档 运行前附加数据库.mdf&#xff08;或sql生成数据库&#xff09; 主要技术&#xff1a; 基于asp.net架构和sql server数据库 功能模块&#xff1a; 首页 会员注册 教练预约 系统公告 健身课程 在线办卡 用户中心[修改个人信息 修…

设计模式-行为型模式-模板方法模式

一、什么是模板模式 模板方法模式&#xff08;Template Method Pattern&#xff09;是一种行为型设计模式&#xff0c;它定义了一个算法骨架&#xff0c;允许子类在不改变算法整体结构的情况下重新定义算法的某些步骤。 主要组成部分&#xff1a; 1、模板方法&#xff08;Templ…

如何在面试中胜出?接口自动化面试题安排上

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

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

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

从一到无穷大 #19 TagTree,倒排索引入手是否是优化时序数据库查询的通用方案?

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作)&#xff0c;由 李兆龙 确认&#xff0c;转载请注明版权。 文章目录 文章主旨时序数据库查询的一般流程扫描维度聚合时间聚合管控语句 TagTree整体结构索引…

ArkTS - HarmonyOS服务卡片(创建)

可以参考官网文档 其中我们在已有的文件中File > New > Service Widget创建你想要的小卡片 本文章发布时目前可使用的模板就三种 有卡片后的new 最终效果

「Verilog学习笔记」实现3-8译码器①

专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点&#xff0c;刷题网站用的是牛客网 分析 ① 本题要求根据38译码器的功能表实现该电路&#xff0c;同时要求采用基础逻辑门实现&#xff0c;那么就需要将功能表转换为逻辑表达式。 timescale 1ns/1nsmodule d…

K8S基础笔记

1、namespace 名称空间用来对集群资源进行隔离划分&#xff0c;默认只隔离资源&#xff0c;不隔离网络k8s默认的名称空间为default 查看k8s的所有命名空间 kubectl get namespace 或者 kubectl get ns 创建名称空间 kubectl create ns 名称 或使用yaml方式 编写yamlkub…

竞赛 题目:基于大数据的用户画像分析系统 数据分析 开题

文章目录 1 前言2 用户画像分析概述2.1 用户画像构建的相关技术2.2 标签体系2.3 标签优先级 3 实站 - 百货商场用户画像描述与价值分析3.1 数据格式3.2 数据预处理3.3 会员年龄构成3.4 订单占比 消费画像3.5 季度偏好画像3.6 会员用户画像与特征3.6.1 构建会员用户业务特征标签…

应试教育导致学生迷信标准答案惯性导致思维僵化-移动机器人

移动机器人课程群实践创新的困境与突围 一、引言 随着科技的快速发展&#xff0c;工程教育变得越来越重要。然而&#xff0c;传统的应试教育模式往往侧重于理论知识的传授&#xff0c;忽视了学生的实践能力和创新精神的培养。这在移动机器人课程群的教学中表现得尤为明显。本文…

05-Spring Boot工程中简化开发的方式Lombok和dev-tools

简化开发的方式Lombok和dev-tools Lombok常用注解 Lombok用标签方式代替构造器、getter/setter、toString()等重复代码, 在程序编译的时候自动生成这些代码 注解名功能NoArgsConstructor生成无参构造方法AllArgsConstructor生产含所有属性的有参构造方法,如果不希望含所有属…