Spring Boot中使用AOP和反射机制设计一个的幂等注解(两种持久化模式),简单易懂教程

该帖子介绍如何设计利用AOP设计幂等注解,且可设置两种持久化模式

1、普通模式:基于redis的幂等注解,持久化程度较低

2、增强模式:基于数据库(MySQL)的幂等注解,持久化程度高

如果只需要具有redis持久化幂等的功能就可以,参考Spring Boot中使用AOP设计一个基于redis的幂等注解,简单易懂教程-CSDN博客

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

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

1、配置依赖、配置redis、创建MySQL表

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所在服务器的ipport: 6378 #替换为自己redis的端口password: # 如果无密码则留空

1.3、使用RDM连接redis 

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

1.4、创建mysql持久化键的表

在springboot连接的mysql数据库上,执行以下语句

-- 创建用于存储幂等键的表
CREATE TABLE idempotent_keys (id BIGINT AUTO_INCREMENT PRIMARY KEY, -- 主键,自增,唯一标识每条记录idempotent_key VARCHAR(255) NOT NULL UNIQUE, -- 幂等键,唯一约束,用于防止重复操作created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- 键的创建时间,默认为当前时间
) COMMENT='存储幂等键的表,用于实现幂等性操作';

创建成功

下一步是写逻辑代码

2、 主要逻辑代码

2.1、创建目录和文件

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

2.2、Idempotent.java

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 幂等性注解* 支持Redis持久和数据库持久模式*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {Mode mode() default Mode.REDIS; // 持久模式:默认Redisenum Mode {REDIS, DATABASE}
}

默认为redis持久模式,可设置为数据库持久模式

2.3、IdempotentAspect.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 IdempotentAspect {private final RedisUtil redisUtil;private final IdempotentDatabaseUtil databaseUtil;public IdempotentAspect(RedisUtil redisUtil, IdempotentDatabaseUtil databaseUtil) {this.redisUtil = redisUtil;this.databaseUtil = databaseUtil;}/*** 定义Pointcut,用于拦截service包中的所有方法*///@Pointcut("execution(* com.xxx.service..*(..)) && @annotation(idempotent)")//可以对下面这一行注释掉,然后使用上面这一行代码,但包的路径需要换@Pointcut("@annotation(idempotent)")public void idempotentMethods(Idempotent idempotent) {}/*** 定义环绕通知,处理幂等性逻辑*/@Around("idempotentMethods(idempotent)")public Object handleIdempotent(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable {// 生成幂等键String key = generateKey(joinPoint);if (key == null || key.isEmpty()) {throw new IllegalArgumentException("无法生成幂等键");}boolean success;if (idempotent.mode() == Idempotent.Mode.REDIS) {success = redisUtil.setIfAbsent(key, "1", 10, TimeUnit.MINUTES);} else {success = databaseUtil.saveKeyIfAbsent(key);}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(idempotent)")

2.4、IdempotentDatabaseUtil.java

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;@Component
public class IdempotentDatabaseUtil {private final JdbcTemplate jdbcTemplate;public IdempotentDatabaseUtil(JdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;}/*** 尝试保存幂等键** @param key 幂等键* @return 如果键不存在并保存成功,则返回true;否则返回false*/public boolean saveKeyIfAbsent(String key) {String sql = "INSERT INTO idempotent_keys (idempotent_key) VALUES (?)";try {jdbcTemplate.update(sql, key);return true; // 插入成功} catch (Exception e) {return false; // 键已存在}}/*** 删除幂等键** @param key 幂等键*/public void deleteKey(String key) {String sql = "DELETE FROM idempotent_keys WHERE idempotent_key = ?";jdbcTemplate.update(sql, key);}/*** 检查是否存在幂等键** @param key 幂等键* @return 存在则返回true,否则返回false*/public boolean exists(String key) {String sql = "SELECT COUNT(1) FROM idempotent_keys WHERE idempotent_key = ?";Integer count = jdbcTemplate.queryForObject(sql, new Object[]{key}, Integer.class);return count != null && count > 0;}/*** 清理过期幂等键(可选,用于定期清理)** @param durationInMinutes 清理指定分钟数之前的键*/public void cleanOldKeys(int durationInMinutes) {String sql = "DELETE FROM idempotent_keys WHERE created_at < NOW() - INTERVAL ? MINUTE";jdbcTemplate.update(sql, durationInMinutes);}
}

2.5、RedisUtil.java

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import java.util.concurrent.TimeUnit;@Component
public class RedisUtil {private final StringRedisTemplate redisTemplate;public RedisUtil(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、在业务代码上测试

3.1、redis持久化模式

@Idempotent(mode = Idempotent.Mode.REDIS)

把上面这一行代码扣在方法头上就能用了,如下 

@Override
@Idempotent(mode = Idempotent.Mode.REDIS)
public <T> ReturnStatus<T> createTask(TaskRequest TaskRequest) {//业务代码        
}

启动项目,使用postman调用createTask接口

结果显示,成功!

查看RDM中redis的数据

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

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

3.2、数据库持久化模式

@Idempotent(mode = Idempotent.Mode.DATABASE)

把上面这一行代码扣在方法头上就能用了,如下 

@Override
@Idempotent(mode = Idempotent.Mode.DATABASE)
public <T> ReturnStatus<T> createTask(TaskRequest TaskRequest) {//业务代码        
}

启动项目,使用postman调用createTask接口

成功执行,查看一下数据库是否有该数据,yes,存在

查看打印出的日志(需要在application.yml中配置,这一步无关紧要),显示了在idempotent_keys这张表插入数据的sql语句

同一个接口同样的参数再调用一次postman

查看idea控制台打印的日志

由于幂等键存在,所以调用失败,符合实际,测试成功!

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

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

相关文章

VSCode+ESP-IDF开发ESP32-S3-DevKitC-1(1)开发环境搭建

VSCodeESP-IDF开发ESP32-S3-DevKitC-1&#xff08;1&#xff09;开发环境搭建 1.开发环境搭建&#xff08;安装ESP-IDF&#xff09;2.开发环境搭建&#xff08;安装VS Code&#xff09;3.开发环境搭建&#xff08;VSCode中安装ESP-IDF插件及配置&#xff09; 1.开发环境搭建&am…

论文分享 | FuzzLLM:一种用于发现大语言模型中越狱漏洞的通用模糊测试框架

大语言模型是当前人工智能领域的前沿研究方向&#xff0c;在安全性方面大语言模型存在一些挑战和问题。分享一篇发表于2024年ICASSP会议的论文FuzzLLM&#xff0c;它设计了一种模糊测试框架&#xff0c;利用模型的能力去测试模型对越狱攻击的防护水平。 论文摘要 大语言模型中…

opencv(c++)----图像的读取以及显示

opencv(c)----图像的读取以及显示 imread: 作用&#xff1a;读取图像文件并将其加载到 Mat 对象中。参数&#xff1a; 第一个参数是文件路径&#xff0c;可以是相对路径或绝对路径。第二个参数是读取标志&#xff0c;比如 IMREAD_COLOR 表示以彩色模式读取图像。 返回值&#x…

用源码编译虚幻引擎,并打包到安卓平台

用源码编译虚幻引擎&#xff0c;并打包到安卓平台 前往我的博客,获取更优的阅读体验 作业内容: 源码编译UE5.4构建C项目&#xff0c;简单设置打包到安卓平台 编译虚幻 5 前置内容 这里需要将 Epic 账号和 Github 账号绑定&#xff0c;然后加入 Epic 邀请的组织&#xff0c…

OpenAI震撼发布:桌面版ChatGPT,Windows macOS双平台AI编程体验!

【雪球导读】 「OpenAI推出ChatGPT桌面端」 OpenAI重磅推出ChatGPT桌面端&#xff0c;全面支持Windows和macOS系统&#xff01;这款新工具为用户在日常生活和工作中提供了前所未有的无缝交互体验。对于那些依赖桌面端进行开发工作的专业人士来说&#xff0c;这一更新带来了令人…

【AIGC】破解ChatGPT!如何使用高价值提示词Prompt提升响应质量

文章目录 为什么高价值提示词如此重要&#xff1f;&#x1f50d;1.1 提升响应的相关性和准确性1.2 节省时间与资源1.3 增强用户体验 了解ChatGPT的工作原理&#x1f9e0;2.1 语言模型的训练过程2.2 上下文理解与生成2.3 限制与挑战 高价值提示词的核心要素✍️3.1 清晰明确的指…

07架构面试题

目录 一、关于合生元的面试题的架构分析的问题 1. 陈述两种方案的优劣 2. 在那些条件下&#xff0c;会选择哪一个方案 3. 你倾向那一种&#xff1f; 4. 如果要实施方案二的&#xff0c;准备步骤和流程 一、关于合生元的面试题的架构分析的问题 1. 陈述两种方案的优劣 方案…

反转链表、链表内指定区间反转

反转链表 给定一个单链表的头结点pHead&#xff08;该头节点是有值的&#xff0c;比如在下图&#xff0c;它的val是1&#xff09;&#xff0c;长度为n&#xff0c;反转该链表后&#xff0c;返回新链表的表头。 如当输入链表{1,2,3}时&#xff0c;经反转后&#xff0c;原链表变…

关于win11电脑连接wifi的同时,开启热点供其它设备连接

背景&#xff1a; 我想要捕获手机流量&#xff0c;需要让手机连接上电脑的热点。那么问题来了&#xff0c;我是笔记本电脑&#xff0c;只能连接wifi上网&#xff0c;此时我的笔记本电脑还能开启热点供手机连接吗&#xff1f;可以。 上述内容&#xff0c;涉及到3台设备&#x…

Linux编辑器 - vim

目录 一、vim 的基本概念 1. 正常/普通/命令模式(Normal mode) 2. 插入模式(Insert mode) 3. 末行模式(last line mode) 二、vim 的基本操作 三、vim 正常模式命令集 1. 插入模式 2. 移动光标 3. 删除文字 4. 复制 5. 替换 6. 撤销上一次操作 7. 更改 8. 调至指定…

靓车汽车销售网站(源码+数据库+报告)

基于SpringBoot靓车汽车销售网站&#xff0c;系统包含两种角色&#xff1a;管理员、用户,系统分为前台和后台两大模块&#xff0c;主要功能如下。 前台功能简介&#xff1a; - 首页&#xff1a;展示网站的概要信息和推荐车辆。 - 车辆展示&#xff1a;展示可供销售的汽车。 - …

数据集-目标检测系列- 花卉 鸡蛋花 检测数据集 frangipani >> DataBall

数据集-目标检测系列- 花卉 鸡蛋花 检测数据集 frangipani >> DataBall DataBall 助力快速掌握数据集的信息和使用方式&#xff0c;会员享有 百种数据集&#xff0c;持续增加中。 贵在坚持&#xff01; 数据样例项目地址&#xff1a; * 相关项目 1&#xff09;数据集…

数据库基础(MySQL)

1. 数据库基础 1.1 什么是数据库 存储数据用文件就可以了&#xff0c;为什么还要弄个数据库? 文件保存数据有以下几个缺点&#xff1a; 文件的安全性问题文件不利于数据查询和管理文件不利于存储海量数据文件在程序中控制不方便 数据库存储介质&#xff1a; 磁盘内存 为…

stm32cubemx+VSCODE+GCC+makefile 开发环境搭建

title: stm32cubemxVSCODEGCCmakefile 开发环境搭建 tags: FreertosHalstm32cubeMx 文章目录 内容往期内容导航第一步准备环境vscode 插件插件配置点灯 内容 往期内容导航 第一步准备环境 STM32CubeMXVSCODEMinGWOpenOcdarm-none-eabi-gcc 然后把上面下载的软件 3 4 5 bin 文…

【网络】网络抓包与协议分析

网络抓包与协议分析 一. 以太网帧格式分析 这是以太网数据帧的基本格式&#xff0c;包含目的地址(6 Byte)、源地址(6 Byte)、类型(2 Byte)、数据(46~1500 Byte)、FCS(4 Byte)。 Mac 地址类型 分为单播地址、组播地址、广播地址。 单播地址&#xff1a;是指第一个字节的最低位…

el-cascader 使用笔记

1.效果 2.官网 https://element.eleme.cn/#/zh-CN/component/cascader 3.动态加载&#xff08;官网&#xff09; <el-cascader :props"props"></el-cascader><script>let id 0;export default {data() {return {props: {lazy: true,lazyLoad (…

一分钟学习数据安全——IAM数据安全的安当实践

数字化进程推进加速&#xff0c;数据已经成为企业最重要的资产。越来越多的企业引入IAM来加强数据安全&#xff0c;确保数据在生产、存储、使用过程中的身份安全。IAM&#xff08;Identity and Access Management&#xff0c;身份与访问管理&#xff09;主要用于管理和控制用户…

使用 Axios 拦截器优化 HTTP 请求与响应的实践

目录 前言1. Axios 简介与拦截器概念1.1 Axios 的特点1.2 什么是拦截器 2. 请求拦截器的应用与实践2.1 请求拦截器的作用2.2 请求拦截器实现 3. 响应拦截器的应用与实践3.1 响应拦截器的作用3.2 响应拦截器实现 4. 综合实例&#xff1a;一个完整的 Axios 配置5. 使用拦截器的好…

c语言数据结构与算法--简单实现线性表(顺序表+链表)的插入与删除

老规矩&#xff0c;点赞评论收藏关注&#xff01;&#xff01;&#xff01; 目录 线性表 其特点是&#xff1a; 算法实现&#xff1a; 运行结果展示 链表 插入元素&#xff1a; 删除元素&#xff1a; 算法实现 运行结果 线性表是由n个数据元素组成的有限序列&#xff…

LeetCode - #139 单词拆分

文章目录 前言摘要1. 描述2. 示例3. 答案题解动态规划的思路代码实现代码解析1. **将 wordDict 转换为 Set**2. **初始化 DP 数组**3. **状态转移方程**4. **返回结果** **测试用例**示例 1:示例 2:示例 3: 时间复杂度空间复杂度总结关于我们 前言 本题由于没有合适答案为以往遗…