每天学习一点点之 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;

    @Override
    public 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;
    }

    @Override
    public 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);
            }
        }
    }

    @Override
    public 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 {

    // Permissions
    String[] 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> {

    @Override
    protected 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;
    }

    @Override
    protected void afterCompletion(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, RequiresPermissions annotation, Exception ex) throws Exception {

    }

    @Override
    protected 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> {

    @Override
    protected 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;
    }

    @Override
    protected void afterCompletion(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, Canyon annotation, Exception ex) throws Exception {

    }

    @Override
    protected 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 端实现代理功能。

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

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

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

相关文章

外汇天眼:每周都能赢奖金?

最近&#xff0c;有不少外汇天眼的用户询问天眼客服&#xff0c;每周举办的外汇天眼模拟比赛是真的能拿到奖金吗&#xff1f;答案是&#xff1a;是的&#xff01;表现优秀者可瓜分350美金&#xff0c;如果周周参加&#xff0c;周周获得名次&#xff0c;那这个奖金也是能叠加获得…

NAS层协议栈学习笔记

NAS(Non-Access Stratum)是无线网络中非接入层及包括移动性管理(MM)和会话管理(SM)协议 &#xff0c;在5G(NR)系统中连接管理(Connection Management)用于建立和释放UE与AMF之间的控制面(CP)信令连接。 5G中移动性管理是通过NAS信令在UE与核心网之间进行交互的&#xff0c;连接…

基于SSM的供电公司安全生产考试系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

AIGC ChatGPT 4 将数据接口文件使用Python进行入库Mysql

数据分析,数据处理的过程,往往将采集到的数据,或者从生产库过来的接口文件,我们都需要进行入库操作。 如下图数据: 将这样的数据接口文件,进行入库,插入到Mysql数据库中。 用Python代码来完成。 ChatGPT4来完成代码输入。 ChatGPT4完整内容如下: 这个任务可以使用`…

牛掰的dd命令,cpi0配合find备份(不会主动备份),od查看

dd if设备1或文件 of设备2或文件 blocknsize countn 还原就是把设备1,2调过来 这里想到dump的还原是命令restore&#xff0c;想起来就写一下&#xff0c;省的总忘记 可以针对整块磁盘进行复制&#xff0c;对于新创建的分区&#xff0c;也不用格式化&#xff0c;可以直接…

Android描边外框stroke边线、rotate旋转、circle圆形图的简洁通用方案,基于Glide与ShapeableImageView,Kotlin

Android描边外框stroke边线、rotate旋转、circle圆形图的简洁通用方案&#xff0c;基于Glide与ShapeableImageView&#xff0c;Kotlin 利用ShapeableImageView专门处理圆形和外框边线的特性&#xff0c;通过Glide加载图片装载到ShapeableImageView。注意&#xff0c;因为要描边…

Burpsuite抓HTTPS证书导入问题

Burpsuite证书导出有两种方法&#xff1a; 第一种方法 1、开启代理后直接在浏览器中输入burp下载CA证书 2、在中间证书颁发机构中导入刚导出的证书 3、导入完成后再把这个证书选择导出&#xff0c;另存为cer格式的文件 4、在受信任的根证书颁发机构中导入刚保存的cer格式证书…

Java Web——JS中的BOM

1. Web API概述 Web API 是指浏览器提供的一套接口&#xff0c;这些接口允许开发人员使用 JavaScript&#xff08;JS&#xff09;来操作浏览器功能和页面元素。通过 Web API&#xff0c;开发人员可以与浏览器进行交互&#xff0c;以实现更复杂的功能和效果。 1.1. 初识Web AP…

基于SSM的高校毕业选题管理系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

渗透测试--实战若依ruoyi框架

免责声明&#xff1a; 文章中涉及的漏洞均已修复&#xff0c;敏感信息均已做打码处理&#xff0c;文章仅做经验分享用途&#xff0c;切勿当真&#xff0c;未授权的攻击属于非法行为&#xff01;文章中敏感信息均已做多层打马处理。传播、利用本文章所提供的信息而造成的任何直…

什么是Mock?为什么要使用Mock呢?

1、前言 在日常开发过程中&#xff0c;大家经常都会遇到&#xff1a;新需求来了&#xff0c;但是需要跟第三方接口来对接&#xff0c;第三方服务还没好&#xff0c;我们自己的功能设计如何继续呢&#xff1f;这里&#xff0c;给大家推荐一下Mock方案。 2、场景示例 2.1、场景一…

Redis-核心数据结构

五种数据结构 String结构 String结构应用场景 Hash结构 Hash结构应用场景 List结构 List结构应用场景 Set结构 Set结构应用场景 ZSet有序结构 ZSet有序结构应用场景

【十字链表,邻接多重表(无向图的另一种链式存储结构),图的遍历】

文章目录 十字链表邻接多重表&#xff08;无向图的另一种链式存储结构&#xff09;图的遍历 十字链表 方便找到入度和出度边。 顶点结点&#xff1a; data&#xff1a;顶点存放的数据域。 firstin&#xff1a;第一个入度边。 firstout&#xff1a;第一个出度边。 弧度结点&am…

Django 入门学习总结6 - 测试

1、介绍自动化测试 测试的主要工作是检查代码的运行情况。测试有全覆盖和部分覆盖。 自动测试表示测试工作由系统自动完成。 在大型系统中&#xff0c;有许多组件有很复杂的交互。一个小的变化可能会带来意想不到的后果 测试能发现问题&#xff0c;并以此解决问题。 测试驱…

ANSYS网格无关性检查

网格精度对应力结果存在很大的影响&#xff0c;有时候可以发现&#xff0c;随着网格精度逐渐提高&#xff0c;所求得的最大应力值逐渐趋于收敛。 默认网格&#xff1a; 从默认网格下计算出的应力云图可以发现&#xff0c;出现了的三处应力奇异点&#xff0c;此时算出的应力值是…

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

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

【C++上层应用】2. 预处理器

文章目录 【 1. #define 预处理 】【 2. #ifdef、#if 条件编译 】2.1 #ifdef2.2 #if2.3 实例 【 3. # 和 ## 预处理 】3.1 # 替换预处理3.2 ## 连接预处理 【 4. 预定义宏 】 预处理器是一些指令&#xff0c;指示编译器在实际编译之前所需完成的预处理。 所有的预处理器指令都是…

【Java 进阶篇】Ajax 实现——JQuery 实现方式 `ajax()`

嗨&#xff0c;亲爱的读者们&#xff01;欢迎来到这篇关于使用 jQuery 中的 ajax() 方法进行 Ajax 请求的博客。在前端开发中&#xff0c;jQuery 提供了简便而强大的工具&#xff0c;其中 ajax() 方法为我们处理异步请求提供了便捷的解决方案。无需手动创建 XMLHttpRequest 对象…

接口测试知识点问答

一.什么是接口&#xff1f; 接口测试主要用于外部系统与系统之间以及内部各个子系统之间的交互点&#xff0c;定义特定的交互点&#xff0c;然后通过这些交互点来&#xff0c;通过一些特殊的规则也就是协议&#xff0c;来进行数据之间的交互。 二.接口都有哪些类型&#xff1f…

四旋翼无人机的飞行原理--【其利天下分享】

近年来&#xff0c;无人机在多领域的便捷应用促使其迅猛的发展&#xff0c;如近年来的多场战争&#xff0c;无人机的战场运用发挥得淋漓尽致。 下面我们针对生活中常见的四旋翼无人机的飞行原理做个基础的介绍&#xff0c;以飨各位对无人机有兴趣的朋友。 一&#xff1a;四旋翼…