当前位置: 首页 > news >正文

基于Redisson,实现分布式锁注解

1.原始写法

我们平常使用redisson的分布式锁是不是基本都用下面的这个模板,既然是模板,那为何不把他抽出来呢?

// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {try {...业务代码} finally {lock.unlock();}
}

2.抽出分布式锁工具类

我们可以抽出一个 LockService 方法,把锁的模板写在方法里,调用的时候只需要指定 key,把锁内的代码块用 supplier 函数传进来。

@Service
@Slf4j
public class LockService {@Autowiredprivate RedissonClient redissonClient;public <T> T executeWithLock(String key, int waitTime, TimeUnit unit, SupplierThrow<T> supplier) throws Throwable {RLock lock = redissonClient.getLock(key);boolean lockSuccess = lock.tryLock(waitTime, unit);if (!lockSuccess) {throw new BusinessException(CommonErrorEnum.LOCK_LIMIT);}try {return supplier.get();//执行锁内的代码逻辑} finally {lock.unlock();}}
}

使用起来就方便了

lockService.executeWithLock(key, 10, TimeUnit.SECONDS, ()->{//执行业务逻辑。。。。。return null;
});

如果我们不需要排队等锁,甚至还能重载方法减少两个参数。

lockService.executeWithLock(key, ()->{//执行业务逻辑。。。。。return null;
});

还能不能更简便呢?当然!

3.注解实现分布式锁

其实锁工具类已经是核心功能代码了,用注解只是为了使用方便。就像很多底层sdk,都是有接口调用的方法来实现核心功能,然后再加个注解让使用更加简便。来想一想场景,我们的分布式锁很多时候都是加在最外层,也就是controller上,或者是service某个方法上。我们通常加锁需要的key,都是由入参组装的。那是不是可以用el表达式来组装key呢?

3.1 创建注解@RedissonLock

/*** 分布式锁注解*/
@Retention(RetentionPolicy.RUNTIME)//运行时生效
@Target(ElementType.METHOD)//作用在方法上
public @interface RedissonLock {/*** key的前缀,默认取方法全限定名,除非我们在不同方法上对同一个资源做分布式锁,就自己指定** @return key的前缀*/String prefixKey() default "";/*** springEl 表达式** @return 表达式*/String key();/*** 等待锁的时间,默认-1,不等待直接失败,redisson默认也是-1** @return 单位秒*/int waitTime() default -1;/*** 等待锁的时间单位,默认毫秒** @return 单位*/TimeUnit unit() default TimeUnit.MILLISECONDS;}

约定大于配置的思想,我们的大多数参数都是可以默认的。很多时候我们的锁都是针对方法的,要锁同一处地方,调用同一个方法就好了,这样前缀可以直接默认根据类+方法名来实现,同样针对特例我们也提供了自己指定前缀的入口。

3.2 实现切面RedissonLockAspect

切面其实很简单,构建key=前缀+el表达式,然后把参数都传进去,调用我们核心功能的工具类LockService。

@Slf4j
@Aspect
@Component
@Order(0)//确保比事务注解先执行,分布式锁在事务外
public class RedissonLockAspect {@Autowiredprivate LockService lockService;@Around("@annotation(com.abin.mallchat.common.common.annotation.RedissonLock)")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();RedissonLock redissonLock = method.getAnnotation(RedissonLock.class);String prefix = StrUtil.isBlank(redissonLock.prefixKey()) ? SpElUtils.getMethodKey(method) : redissonLock.prefixKey();//默认方法限定名+注解排名(可能多个)String key = SpElUtils.parseSpEl(method, joinPoint.getArgs(), redissonLock.key());return lockService.executeWithLockThrows(prefix + ":" + key, redissonLock.waitTime(), redissonLock.unit(), joinPoint::proceed);}
}

上述解析EL表达式需要定义以下解析类

import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;import java.lang.reflect.Method;
import java.util.Optional;/*** Description: spring el表达式解析*/
public class SpElUtils {private static final ExpressionParser parser = new SpelExpressionParser();private static final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();public static String parseSpEl(Method method, Object[] args, String spEl) {//解析参数名String[] params = Optional.ofNullable(parameterNameDiscoverer.getParameterNames(method)).orElse(new String[]{});EvaluationContext context = new StandardEvaluationContext();//el解析需要的上下文对象for (int i = 0; i < params.length; i++) {context.setVariable(params[i], args[i]);//所有参数都作为原材料扔进去}Expression expression = parser.parseExpression(spEl);return expression.getValue(context, String.class);}public static String getMethodKey(Method method) {return method.getDeclaringClass() + "#" + method.getName();}
}

3.3 使用

以mallchat项目为例,使用起来就非常方便了,发奖的时候,我们需要对uid加锁,直接一个注解搞定。如果需要等待,再加个等待时间就行。这里需要注意,分布式锁要在事务外层。所以我们锁的切面优先级要高一些。

@Service
public class UserBackpackServiceImpl implements IUserBackpackService {@Autowiredprivate UserBackpackDao userBackpackDao;@Autowiredprivate ItemCache itemCache;@Autowiredprivate ApplicationEventPublisher applicationEventPublisher;@Autowired@Lazyprivate UserBackpackServiceImpl userBackpackService;@Overridepublic void acquireItem(Long uid, Long itemId, IdempotentEnum idempotentEnum, String businessId) {//组装幂等号String idempotent = getIdempotent(itemId, idempotentEnum, businessId);userBackpackService.doAcquireItem(uid, itemId, idempotent);}@RedissonLock(key = "#idempotent", waitTime = 5000)//相同幂等如果同时发奖,需要排队等上一个执行完,取出之前数据返回public void doAcquireItem(Long uid, Long itemId, String idempotent) {UserBackpack userBackpack = userBackpackDao.getByIdp(idempotent);//幂等检查if (Objects.nonNull(userBackpack)) {return;}//业务检查ItemConfig itemConfig = itemCache.getById(itemId);if (ItemTypeEnum.BADGE.getType().equals(itemConfig.getType())) {//徽章类型做唯一性检查Integer countByValidItemId = userBackpackDao.getCountByValidItemId(uid, itemId);if (countByValidItemId > 0) {//已经有徽章了不发return;}}//发物品UserBackpack insert = UserBackpack.builder().uid(uid).itemId(itemId).status(YesOrNoEnum.NO.getStatus()).idempotent(idempotent).build();userBackpackDao.save(insert);//用户收到物品的事件applicationEventPublisher.publishEvent(new ItemReceiveEvent(this, insert));}private String getIdempotent(Long itemId, IdempotentEnum idempotentEnum, String businessId) {return String.format("%d_%d_%s", itemId, idempotentEnum.getType(), businessId);}
}

相关文章:

基于Redisson,实现分布式锁注解

1.原始写法 我们平常使用redisson的分布式锁是不是基本都用下面的这个模板&#xff0c;既然是模板&#xff0c;那为何不把他抽出来呢&#xff1f; // 尝试加锁&#xff0c;最多等待100秒&#xff0c;上锁以后10秒自动解锁 boolean res lock.tryLock(100, 10, TimeUnit.SECON…...

【机器学习】机器学习是什么?

你知道机器学习是什么吗&#xff1f;它就像是一个超级聪明的孩子&#xff0c;可以通过观察和经验不断学习和成长。而我们要做的就是培养和教育这个孩子&#xff0c;让他能够从数据中学习并做出决策和预测。 那么&#xff0c;我们该如何培养和教育这个聪明的孩子呢&#xff1f;首…...

一文速览深度伪造检测(Detection of Deepfakes):未来技术的守门人

一文速览深度伪造检测&#xff08;Detection of Deepfakes&#xff09;&#xff1a;未来技术的守门人 前言一、Deepfakes技术原理卷积神经网络&#xff08;CNN&#xff09;&#xff1a;细致的艺术学徒生成对抗网络&#xff08;GAN&#xff09;&#xff1a;画家与评审的双重角色…...

C# 中的执行表达式树(Expression Tree)

引言&#xff1a; 在C#编程中&#xff0c;表达式树&#xff08;Expression Tree&#xff09;是一种强大的工具&#xff0c;用于表示和执行计算表达式。表达式树将计算表达式抽象为树状结构&#xff0c;每个节点代表表达式中的一个元素&#xff0c;如常量、变量、方法调用等。本…...

森林监测VR虚拟情景再现系统更便利

AI人工智能技术已经逐渐渗透到各个领域&#xff0c;为我们的生活带来了诸多便利。在虚拟仿真教学领域&#xff0c;AI技术的应用也日益丰富&#xff0c;为虚拟情景交互体验带来了前所未有的好处。 提高VR虚拟情景的逼真度 通过深度学习和计算机视觉等技术&#xff0c;AI/VR虚拟现…...

高频面试题整理(一)

文章目录 平台无关性如何实现&#xff1f;JVM如何加载 .class文件&#xff1f;什么是反射?谈谈ClassLoader谈谈类的双亲委派机制类的加载方式Java的内存模型?JVM内存模型-jdk8程序计数器&#xff1a;Java虚拟机栈局部变量表和操作数栈&#xff1a; Java内存模型中堆和栈的区别…...

2-23 switch、JVM内存模型、垃圾回收机制、this、static、变量的分类

文章目录 switch 实现成绩评级JVM内存模型概念栈的特点堆的特点 垃圾回收机制通用的分代垃圾回收机制三种清理算法垃圾回收过程垃圾回收常见的两种检测引用算法内存泄露常见原因 this的用法创建对象的四步 static 静态特点 变量的分类和作用域import switch 实现成绩评级 switc…...

基础!!!吴恩达deeplearning.ai:卷积层

以下内容有任何不理解可以翻看我之前的博客哦&#xff1a;吴恩达deeplearning.ai专栏 文章目录 回顾——密集层 Dense Layer卷积层 Convolutional Neural Network定义优势具体说明心电图卷积层搭建 到目前为止&#xff0c;你使用的所有神经网络层都是密集层类型&#xff0c;这…...

SpringBoot案例(黑马学习笔记)

这个案例呢&#xff0c;就是Tlias智能学习辅助系统。 参考接口文档完成后端功能的开 发&#xff0c;然后结合前端工程进行联调测试即可。 完成后的成品效果展示&#xff1a; 准备工作 需求&环境搭建 需求说明 部门管理 部门管理功能开发包括&#xff1a; ● 查询部门列…...

项目流程图

实现便利店自助付款项目 服务器&#xff1a; 1、并发服务器&#xff08;多进程、多线程、IO多路复用&#xff09; 2、SQL数据库的创建和使用&#xff08;增删改查&#xff09; 3、以模块化编写项目代码&#xff0c;按照不同模块编写.h/.c文件 客户端&#xff1a; 1、QT客户端界…...

鸿蒙这么大声势,为何迟迟看不见岗位?最新数据来了

对于鸿蒙生态建设而言&#xff0c;2024年可谓至关重要&#xff0c;而生态建设的前提&#xff0c;就是要有足够的开发人才。与之对应的&#xff0c;今年春招市场上与鸿蒙相关岗位和人才旺盛的热度&#xff0c;一方面反应了鸿蒙生态的逐渐壮大&#xff0c;另一方面也让人们对鸿蒙…...

Qt中关于信号与槽函数的思考

信号与槽函数的思考 以pushbutton控件为例&#xff0c;在主界面上放置一个pushbutton控件&#xff0c;点击右键选择关联槽函数&#xff0c;关联一个click函数&#xff0c;如下图所示&#xff1a; 在该函数中&#xff0c;实现了一个点击pushbutton按钮后&#xff0c;弹出一个窗…...

项目技术栈-解决方案-消息队列

项目技术栈-解决方案-消息队列 概念应用场景1. 异步处理 参考文章消息队列&#xff08;Message Queue&#xff09; 概念 “消息”是在两台计算机间传送的数据单位。 消息可以非常简单&#xff0c;例如只包含文本字符串&#xff1b; 也可以更复杂 &#xff0c;包括对象等。 队…...

【深度优先搜索】【图论】【推荐】332. 重新安排行程

作者推荐 动态规划的时间复杂度优化 本文涉及知识点 深度优先搜索 图论 LeetCode332. 重新安排行程 给你一份航线列表 tickets &#xff0c;其中 tickets[i] [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。 所有这些机票都属于一个从 JFK&a…...

DAY9-防病毒AV概述

DNS过滤 URL过滤和DNS过滤对比...

TCP缓存

TCP缓存是指TCP协议在数据传输过程中使用的一种机制&#xff0c;用于临时存储和管理数据包。它主要有三个作用&#xff1a;提高网络性能、保证数据的可靠性和实现流量控制。 首先&#xff0c;TCP缓存可以提高网络性能。当发送端发送数据时&#xff0c;TCP协议会将数据分割成若…...

Socket网络编程(一)——网络通信入门基本概念

目录 网络通信基本概念什么是网络&#xff1f;网络通信的基本架构什么是网络编程?7层网络模型-OSI模型什么是Socket&#xff1f;Socket的作用和组成Socket传输原理Socket与TCP、UDP的关系CS模型(Client-Server Application)报文段牛刀小试&#xff08;TCP消息发送与接收&#…...

RTCA DO-178C 机载系统和设备认证中的软件注意事项-软件质量保证流程(八)

8.0 软件质量保证流程 SOFTWARE QUALITY ASSURANCE PROCESS 本节讨论软件质量保证 (SQA) 过程的目标和活动。 SQA 流程按照软件规划流程&#xff08;参见 4&#xff09;和软件质量保证计划&#xff08;参见 11.5&#xff09;的定义进行应用。 SQA 过程活动的输出记录在软件质量…...

K 个一组翻转链表 力扣

【玩转校招算法面试】第三天&#xff1a;链表中的节点每k个一组翻转&#xff08;动画演示、手写 Java 代码、详细注释、LeetCode 高频算法题&#xff09;_哔哩哔哩_bilibili 初始状态&#xff1a;1 -> 2 -> 3&#xff0c;pre null, cur 1保存当前节点的下一个节点&…...

Java毕业设计 基于SSM SpringBoot vue购物比价网站

Java毕业设计 基于SSM SpringBoot vue购物比价网站 SSM vue 购物比价网站 功能介绍 首页 图片轮播 商品 商品分类 商品详情 评论 收藏 商品攻略 商品信息 公告栏 在线反馈 登录 注册 个人中心 我的收藏 后台管理 登录 注册商品户 个人中心 修改密码 个人信息 商品户管理 用户…...

[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?

&#x1f9e0; 智能合约中的数据是如何在区块链中保持一致的&#xff1f; 为什么所有区块链节点都能得出相同结果&#xff1f;合约调用这么复杂&#xff0c;状态真能保持一致吗&#xff1f;本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里&#xf…...

使用VSCode开发Django指南

使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架&#xff0c;专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用&#xff0c;其中包含三个使用通用基本模板的页面。在此…...

ESP32读取DHT11温湿度数据

芯片&#xff1a;ESP32 环境&#xff1a;Arduino 一、安装DHT11传感器库 红框的库&#xff0c;别安装错了 二、代码 注意&#xff0c;DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...

高等数学(下)题型笔记(八)空间解析几何与向量代数

目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

【python异步多线程】异步多线程爬虫代码示例

claude生成的python多线程、异步代码示例&#xff0c;模拟20个网页的爬取&#xff0c;每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程&#xff1a;允许程序同时执行多个任务&#xff0c;提高IO密集型任务&#xff08;如网络请求&#xff09;的效率…...

【JavaSE】绘图与事件入门学习笔记

-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角&#xff0c;以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向&#xff0c;距离坐标原点x个像素;第二个是y坐标&#xff0c;表示当前位置为垂直方向&#xff0c;距离坐标原点y个像素。 坐标体系-像素 …...

MySQL 8.0 事务全面讲解

以下是一个结合两次回答的 MySQL 8.0 事务全面讲解&#xff0c;涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容&#xff0c;并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念&#xff08;ACID&#xff09; 事务是…...

LabVIEW双光子成像系统技术

双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制&#xff0c;展现出显著的技术优势&#xff1a; 深层组织穿透能力&#xff1a;适用于活体组织深度成像 高分辨率观测性能&#xff1a;满足微观结构的精细研究需求 低光毒性特点&#xff1a;减少对样本的损伤…...

MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用

文章目录 一、背景知识&#xff1a;什么是 B-Tree 和 BTree&#xff1f; B-Tree&#xff08;平衡多路查找树&#xff09; BTree&#xff08;B-Tree 的变种&#xff09; 二、结构对比&#xff1a;一张图看懂 三、为什么 MySQL InnoDB 选择 BTree&#xff1f; 1. 范围查询更快 2…...

从面试角度回答Android中ContentProvider启动原理

Android中ContentProvider原理的面试角度解析&#xff0c;分为​​已启动​​和​​未启动​​两种场景&#xff1a; 一、ContentProvider已启动的情况 1. ​​核心流程​​ ​​触发条件​​&#xff1a;当其他组件&#xff08;如Activity、Service&#xff09;通过ContentR…...