Spring Boot中使用AOP和反射机制设计一个基于redis的幂等注解,简单易懂教程

news2024/11/22 1:20:52

由于对于一些非查询操作,有时候需要保证该操作是幂等的,该帖子设计幂等注解的原理是使用AOP和反射机制获取方法的类、方法和参数,然后拼接形成一个幂等键,当下一次有重复操作过来的时候,判断该幂等键是否存放,如果存在则为”重复操作“,不继续执行;如果不存在,则为”第一次操作“,可以执行。

javaer可以在自己的项目中,加入这个点,增加项目的亮点。

1、配置依赖、配置redis

1.1、在pom文件中加入依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>


1.2、配置redis地址


如何安装redis、获取redis的ip地址,以及redis可视化工具RDM的使用,参考以下博客的1、2点Spring Boot项目中加入布隆过滤器————实战-CSDN博客

如果不想使用docker容器安装redis,可以自己下载安装redis。

spring:
  redis:
    host: 192.168.57.111 #替换为自己redis所在服务器的ip
    port: 6378 #替换为自己redis的端口
    password: # 如果无密码则留空


1.3、使用RDM连接redis 


如何连接,参考以下博客的1、2点Spring Boot项目中加入布隆过滤器————实战-CSDN博客

 

2、主要逻辑代码

2.1、创建目录和文件

创建类似的目录结构,util与service同一级即可,并如下创建3个java文件

2.2、RedisIdempotent.java

/**
 * Redis幂等性注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisIdempotent {
}

2.3、handleRedisIdempotent.java



import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
public class RedisIdempotentAspect {

    private final RedisUtils redisUtils;

    public RedisIdempotentAspect(RedisUtils redisUtils) {
        this.redisUtils = redisUtils;
    }

    /**
     * 定义Pointcut,用于拦截service包中的所有方法
     */
    @Pointcut("@annotation(redisIdempotent)")
    public void redisIdempotentMethods(RedisIdempotent redisIdempotent) {
    }

    /**
     * 定义环绕通知,处理幂等性逻辑
     */
    @Around("redisIdempotentMethods(redisIdempotent)")
    public Object handleRedisIdempotent(ProceedingJoinPoint joinPoint, RedisIdempotent redisIdempotent) throws Throwable {
        // 生成幂等键
        String key = generateKey(joinPoint);
        if (key == null || key.isEmpty()) {
            throw new IllegalArgumentException("无法生成幂等键");
        }

        boolean success = redisUtils.setIfAbsent(key, "1", 10, TimeUnit.MINUTES);

        if (!success) {
            throw new IllegalStateException("重复操作");
            // //这里可使用自己定义的结果返回类包裹信息,就可以不抛出错误

        }

        try {
            return joinPoint.proceed();
        } finally {
            // 可选:操作完成后清理key,视业务需求决定是否需要
        }
    }

    /**
     * 动态生成幂等键
     */
    private String generateKey(ProceedingJoinPoint joinPoint) {
        // 获取类名
        String className = joinPoint.getTarget().getClass().getSimpleName();
        // 获取方法名
        String methodName = joinPoint.getSignature().getName();
        // 获取参数
        Object[] args = joinPoint.getArgs();
        String argsString = Arrays.toString(args);

        // 原始键内容
        String rawKey = String.format("%s:%s:%s", className, methodName, argsString);

        // 对键进行MD5编码
        return "IDEMPOTENT:" + md5(rawKey);
    }

    private String md5(String input) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] hashBytes = md.digest(input.getBytes());
            StringBuilder hexString = new StringBuilder();
            for (byte b : hashBytes) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) hexString.append('0');
                hexString.append(hex);
            }
            return hexString.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("MD5算法不可用", e);
        }
    }
}

在该代码里,使用的是这个,虽然可以用但不严谨,因为更严谨一点,我们只运行幂等注解被我们的几题的service类里的方法使用,因为如果用在其他类的方法上的话,会造成同一个操作出现两个不同的幂等键,造成混乱。
@Pointcut("@annotation(idempotent)")

所以建议这一行注释掉,然后使用下面面这一行代码,但包的路径需要换

@Pointcut("execution(* com.xxx.service..*(..)) && @annotation(redisIdempotent)")

2.4、RedisUtils

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class RedisUtils {

    private final StringRedisTemplate redisTemplate;

    public RedisUtils(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public boolean setIfAbsent(String key, String value, long timeout, TimeUnit unit) {
        Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
        return result != null && result;
    }

    public void delete(String key) {
        redisTemplate.delete(key);
    }
}

3、在业务代码上测试

@RedisIdempotent

只要把注解扣在方法头上就能用了,如下 

@Override
@RedisIdempotent
public <T> ReturnStatus<T> createTask(TaskRequest TaskRequest) {
    //业务代码        
}

以下是演示,使用postman调用createTask方法

查看RDM中redis的数据

redis幂等键存在,同一个接口同样的参数再调用一次postman

因为已经存在幂等键了,调用失败,再查看idea控制台打印的日志,有”重复操作“的信息,符合实际,测试成功

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

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

相关文章

一文详细深入总结服务器选型

1. 题记&#xff1a; 服务器选型工作是项目规划检讨的一项非常重要的工作&#xff0c;本文详细深入总结服务器选型。 2. 服务器基础知识概览 2.1 服务器的定义与功能 2.1 .1 定义 服务器是一种高性能计算机&#xff0c;其设计目的是在网络中提供服务。它可以处理来自多个客…

接口测试用例设计的关键步骤与技巧解析!

简介 接口测试在需求分析完成之后&#xff0c;即可设计对应的接口测试用例&#xff0c;然后根据用例进行接口测试。接口测试用例的设计也需要用到黑盒测试用例设计方法&#xff0c;和测试流程与理论章节的功能测试用例设计的方法类似&#xff0c;设计过程中还需要增加与接口特…

WPF下 DataGrid加入序号列

先上代码&#xff1a; <DataGrid Name"DGV" AutoGenerateColumns"False" Grid.Row"0" Grid.Column"0" HorizontalGridLinesBrush"RoyalBlue" VerticalGridLinesBrush"Tomato" CanUserAddRows"False&qu…

WebSocket详解、WebSocket入门案例

目录 1.1 WebSocket介绍 http协议&#xff1a; webSocket协议&#xff1a; 1.2WebSocket协议&#xff1a; 1.3客户端&#xff08;浏览器&#xff09;实现 1.3.2 WebSocket对象的相关事宜&#xff1a; 1.3.3 WebSOcket方法 1.4 服务端实现 服务端如何接收客户端发送的请…

大模型本地部署实践:Ollama+Open-WebUI(MacOS)

目录 什么是Ollama Ollama安装 对话界面可视化&#xff1f;Open-WebUI&#xff01; 安装Open-WebUI 什么是Ollama Ollama是一个为简化大语言模型本地部署与交互的开源框架。它提供了用户友好的接口&#xff0c;帮助开发者和模型爱好者在没有依赖外部API的基础上高效地运行、…

12万字 | 企业智慧数字化运营平台重构建设项目实施技术方案

本项目旨在推动企业数字化运营平台的重构&#xff0c;以支持组织改革和数字化转型战略的实施&#xff0c;提升企业智慧化运营管理能力。项目将优化订单受理流程&#xff0c;增强业务受理能力&#xff0c;提高客户服务体验和内部管控能力&#xff0c;同时提升营销资源管理的制度…

基于Lora通讯加STM32空气质量检测WIFI通讯-分享

目录 目录 前言 一、本设计主要实现哪些很“开门”功能&#xff1f; 二、电路设计原理图 1.电路图采用Altium Designer进行设计&#xff1a; 2.实物展示图片 三、程序源代码设计 四、获取资料内容 前言 随着环境污染问题的日益严重&#xff0c;空气质量的监测与管理已经…

cesium for unity的使用

先聊聊导入 看到这里的因该能够知道&#xff0c;官网以及网上绝大多数的方法都导入不进来&#xff0c;那么解决方法如下: 两个链接&#xff1a;按照顺序依次下载这两个tgz和zip&#xff0c;其中tgz为主要部分&#xff0c;zip为示例工程项目 如果您要查看示例工程项目的话&am…

数据结构_图的遍历

深度优先搜索遍历 遍历思想 邻接矩阵上的遍历算法 void Map::DFSTraverse() {int i, v;for (i 0; i < MaxLen; i){visited[i] false;}for (i 0; i < Vexnum; i){// 如果顶点未访问&#xff0c;则进行深度优先搜索if (visited[i] false){DFS(i);}}cout << endl…

MySQL时间字段TIMESTAMP和DATETIME

SELECT global.time_zone, session.time_zone;查询数据库的全局时区和当前会话的时区信息&#xff0c;一般如果使用navicat进行连接&#xff0c;没有显示指定时区信息&#xff0c;会默认使用system_time_zone。 可以使用 SET time_zone 08:00; SELECT global.time_zone, sess…

AQS 理解 及不可重入锁实现

由于目前水平有限&#xff0c;只是写出作者目前对 aqs的简单理解&#xff0c;有错误还请评论指证 AQS是什么 Aqs是java.util.concurrent 包下的一个抽象队列 同步器类&#xff0c;被简写为英文AQS&#xff0c; 我认为 可以把他理解 为一个 实现 自定义 锁的 一个具体的框架 …

nginx源码安装配置ssl域名

nginx源码安装 下载 wget http://nginx.org/download/nginx-1.24.0.tar.gz 解压 tar -zxvf nginx-1.24.0.tar.gz 下载openssl apt install openssl 安装nginx cd nginx-1.24.0 sudo apt-get install libpcre3 libpcre3-dev ./configure --prefix=/home/nginx24 --with-http_ss…

【笔记】Android Gradle Plugin配置文件相关说明-libs.versions.toml

版本号 文件路径&#xff1a;Project\gradle\libs.versions.toml 直接搜索versions.agp是找不到的&#xff0c;这是变量引用的写法&#xff0c;查询 agp版本可以直接查版本号。 [versions] agp "8.5.0-alpha08" junit "4.13.2" junitVersion "1.…

支付宝租赁小程序的优势与应用前景分析

内容概要 在这个快节奏的时代&#xff0c;租赁服务越来越成为大家生活中的一部分。而支付宝租赁小程序正是这个大潮流中的一颗璀璨明珠。通过简单易用的界面和强大的功能&#xff0c;这个小程序不仅让用户在租赁过程中获得了前所未有的便利&#xff0c;也为商家提供了新的商业…

三十一、构建完善微服务——API 网关

一、API 网关基础 系统拆分为微服务后&#xff0c;内部的微服务之间是互联互通的&#xff0c;相互之间的访问都是点对点的。如果外部系统想调用系统的某个功能&#xff0c;也采取点对点的方式&#xff0c;则外部系统会非常“头大”。因为在外部系统看来&#xff0c;它不需要也没…

深入理解 Redis跳跃表 Skip List 原理|图解查询、插入

1. 简介 跳跃表 ( skip list ) 是一种有序数据结构&#xff0c;通过在每个节点中维持多个指向其他节点的指针&#xff0c;从而达到快速访问节点的目的。 在 Redis 中&#xff0c;跳跃表是有序集合键的底层实现之一&#xff0c;那么这篇文章我们就来讲讲跳跃表的实现原理。 2. …

MediaSession学习总结

1.框架预览 2.用法 2.1参考链接&#xff1a; MediaSession 简单使用 2.2 服务端要实现MediaBrowserService&#xff1b; 主要实现的功能&#xff1a; mPlaybackState new PlaybackStateCompat.Builder().setState(PlaybackStateCompat.STATE_NONE, currentPostion, 1.0f).…

学习大数据DAY61 宽表加工

目录 模型设计 加工宽表 任务调度&#xff1a; 大表 - 把很多数据整合起来 方便后续的明细查询和指标计算 模型设计 设计 建模 设计: excel 文档去编写 建模: 使用建模工具 PowerDesigner Navicat 在线画图工具... 把表结构给绘 制出来 共享\项目课工具\pd 加工宽表 数…

零基础入门Flink,掌握基本使用方法

Flink基本概念 首先来讲&#xff0c;Flink是一个面向数据流处理和批处理的分布式开源计算框架。 那么&#xff0c;流处理和批处理分别处理什么样的数据呢&#xff0c;这就涉及两个概念-无界流和有界流 无界流VS有界流 任何类型的数据都可以形成流数据&#xff0c;比如用户…

Linux设置以及软件的安装(hadoop集群安装02)

一、Linux的常见设置 1、设置静态IP vi /etc/sysconfig/network-scripts/ifcfg-ens33 如何查看自己的虚拟机的网关&#xff1a; 完整的配置&#xff08;不要拷贝我的&#xff09;&#xff1a; TYPE"Ethernet" PROXY_METHOD"none" BROWSER_ONLY"no&…