【Redisson】基于自定义注解的Redisson分布式锁实现
前言
在项目中,经常需要使用Redisson分布式锁来保证并发操作的安全性。在未引入基于注解的分布式锁之前,我们需要手动编写获取锁、判断锁、释放锁的逻辑,导致代码重复且冗长。为了简化这一过程,我们引入了基于注解的分布式锁,通过一个注解就可以实现获取锁、判断锁、处理完成后释放锁的逻辑。这样可以大大简化代码,提高开发效率。
目标
使用@DistributedLock即可实现获取锁,判断锁,处理完成后释放锁的逻辑。
@RestController
public class HelloController {@DistributedLock@GetMapping("/helloWorld")public void helloWorld() throws InterruptedException {System.out.println("helloWorld");Thread.sleep(100000);}
}
涉及知识
- SpringBoot
- Spring AOP
- Redisson
- 自定义注解
- 统一异常处理
- SpEL表达式
代码实现
引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.21.3</version>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency>
注解类
/*** 分布式锁注解* @author 只有影子*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {/*** 获取锁失败时,默认的错误描述*/String errorDesc() default "任务正在处理中,请耐心等待";/*** SpEL表达式,用于获取锁的key* 示例:* "#name"则从方法参数中获取name的值作为key* "#user.id"则从方法参数中获取user对象中的id作为key*/String[] keys() default {};/*** key的前缀,为空时取类名+方法名*/String prefix() default "";
}
切面类
/*** 分布式锁切面类* @author 只有影子*/
@Slf4j
@Aspect
@Component
public class DistributedLockAspect {@Resourceprivate RedissonClient redissonClient;private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();@Around("@annotation(distributedLock)")public Object around(ProceedingJoinPoint joinPoint,DistributedLock distributedLock) throws Throwable {String redisKey = getRedisKey(joinPoint, distributedLock);log.info("拼接后的redisKey为:" + redisKey);RLock lock = redissonClient.getLock(redisKey);if (!lock.tryLock()) {// 可以使用自己的异常类,演示用RuntimeExceptionthrow new RuntimeException(distributedLock.errorDesc());}// 执行被切面的方法try {return joinPoint.proceed();} finally {lock.unlock();}}/*** 动态解密参数,拼接redisKey* @param joinPoint* @param distributedLock 注解* @return*/private String getRedisKey(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();EvaluationContext context = new MethodBasedEvaluationContext(TypedValue.NULL, method, joinPoint.getArgs(), PARAMETER_NAME_DISCOVERER);StringBuilder redisKey = new StringBuilder();// 拼接redis前缀if (StringUtil.isNotBlank(distributedLock.prefix())) {redisKey.append(distributedLock.prefix()).append(":");} else {// 获取类名String className = joinPoint.getTarget().getClass().getSimpleName();// 获取方法名String methodName = joinPoint.getSignature().getName();redisKey.append(className).append(":").append(methodName).append(":");}ExpressionParser parser = new SpelExpressionParser();for (String key : distributedLock.keys()) {// keys是个SpEL表达式Expression expression = parser.parseExpression(key);Object value = expression.getValue(context);redisKey.append(ObjectUtils.nullSafeToString(value));}return redisKey.toString();}
}
统一异常处理类
/*** 全局异常处理类* @author 只有影子*/
@RestControllerAdvice
public class ExceptionHandle {@ExceptionHandler(Exception.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public String sendErrorResponseSystem(Exception e) {// 这里只是模拟返回值,实际项目中一般都是返回封装好的统一返回类return e.getMessage();}
}
还需要将redis配置读入,这里就不体现
使用示例
1. 无参方法或者需要加方法级的锁
@DistributedLock
@GetMapping("/helloWorld")
public void helloWorld() throws InterruptedException {System.out.println("helloWorld");Thread.sleep(100000);
}
调用接口:http://localhost:8080/helloWorld
拼接后的redisKey为:HelloController:helloWorld:
可以看到,无参方法的key为HelloController:helloWorld:,其中HelloController为类名,helloWorld为方法名,因为是无参方法,所以没有接下来的参数。
这时候,再次调用改接口,则不会再进去接口,会被切面类直接拦截,返回如下结果:

在实际生产使用中,这种情况一般被用来在自动任务上标注,因为在集群环境中自动任务同一时间一般只需要启动一个。
2. 有参数方法,其中key从name中取值
@DistributedLock(keys = "#name")
@GetMapping("/hello1")
public String hello1(String name) throws InterruptedException {String s = "hello " + name;System.out.println(s);Thread.sleep(100000);return s;
}
调用接口为:http://localhost:8080/hello1?name=hurry
拼接后的redisKey为:HelloController:hello1:hurry
这时候,再通过hurry这个名称调用时,就不会再处理,而name换为zhangsan时,则就能正常进入接口。
这时候redis中的key为
> 127.0.0.1@6379 connected!
> keys *
HelloController:hello2:zhangsan
HelloController:hello2:hurry
实际业务中,需要根据不同的参数值进行加锁的场景。
3. 有参数方法,其中key需要从user对象中获取name
@DistributedLock(keys = "#user.name")
@GetMapping("/hello2")
public String hello2(User user) throws InterruptedException {String s = "hello " + user.getName();System.out.println(s);Thread.sleep(100000);return s;
}
需要从某个对象中获取指定属性作为key的场景
4.有参数方法,其中key从name上取值并指定前缀
@DistributedLock(keys = "#name",prefix = "testPrefix")
@GetMapping("/hello3")
public String hello3(String name) throws InterruptedException {String s = "hello " + name;System.out.println(s);Thread.sleep(100000);return s;
}
需要指定key前缀的场景
最后
由于文章篇幅原因,很多东西没有深入的讲解,但是基于以上代码基本实现了基于注解的分布式锁,可以大大提到开发效率。如果还有其他需要拓展的功能,可以通过在注解类增加属性及在切面类中通过不同的属性进行不同的处理来实现。
相关文章:
【Redisson】基于自定义注解的Redisson分布式锁实现
前言 在项目中,经常需要使用Redisson分布式锁来保证并发操作的安全性。在未引入基于注解的分布式锁之前,我们需要手动编写获取锁、判断锁、释放锁的逻辑,导致代码重复且冗长。为了简化这一过程,我们引入了基于注解的分布式锁&…...
QT中样式表常见属性与颜色的设置与应用
常见样式表属性 在Qt中的样式表(QSS)中,有一些特定的英文单词和关键字用于指定不同的样式属性。以下是常见的一些英文单词和关键字: 颜色(Colors): color: 文本颜色 background-color: 背景颜色 border-color: 边框颜色 字体(Fonts): font: 字体 font-family: 字体…...
OpenCvSharp从入门到实践-(02)图像处理的基本操作
目录 图像处理的基础操作 1、读取图像 1.1、读取当前目录下的图像 2、显示图像 2.1、Cv2.ImShow 用于显示图像。 2.2、Cv2.WaitKey方法用于等待用户按下键盘上按键的时间。 2.3、Cv2.DestroyAllWindows方法用于销毁所有正在显示图像的窗口。 2.4实例1-显示图像 2.4实例…...
Spring Boot 升级3.x 指南
Spring Boot 升级3.x 指南 1. 升级思路 先创建一个parent项目,打包类型为pom,继承自spring boot的parent项目 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId&…...
使用支付宝的沙箱环境在本地配置模拟支付并发布至公网调试
文章目录 前言1. 下载当面付demo2. 修改配置文件3. 打包成web服务4. 局域网测试5. 内网穿透6. 测试公网访问7. 配置二级子域名8. 测试使用固定二级子域名访问9. 结语 前言 在沙箱环境调试支付SDK的时候,往往沙箱环境部署在本地,局限性大,在沙…...
python-opencv划痕检测
python-opencv划痕检测 这次实验,我们将对如下图片进行划痕检测,其实这个比较有难度,因为清晰度太差了。 我们做法如下: (1)读取图像为灰度图像,进行自适应直方图均衡化处理,增强图…...
微服务学习|Gateway网关:网关作用、快速入门、路由断言工厂、路由过滤器配置、全局过滤器、过滤器执行顺序、跨域问题处理
为什么需要网关 网关功能: 1.身份认证和权限校验 2.服务路由、负载均衡 3.请求限流 网关的技术实现 在SpringCloud中网关的实现包括两种:gateway、zuul Zuul是基于Servlet的实现,属于阻塞式编程。而SprinaCloudGateway则是基于Spring5中提供的WebFlux…...
七、通过libfdk_aac编解码器实现aac音频和pcm的编解码
前言 测试环境: ffmpeg的4.3.2自行编译版本windows环境qt5.12 AAC编码是MP3格式的后继产品,通常在相同的比特率下可以获得比MP3更高的声音质量,是iPhone、iPod、iPad、iTunes的标准音频格式。 AAC相较于MP3的改进包含: 更多的采…...
spring 是如何开启事务的, 核心原理是什么
文章目录 spring 是如何开启事务的核心原理1 基于注解开启事务2 基于代码来开启事务 spring 是如何开启事务的 核心原理 Spring事务管理的实现有许多细节,如果对整个接口框架有个大体了解会非常有利于我们理解事务,下面通过讲解Spring的事务接口来了解…...
头歌——操作系统实训总结
死锁 1、系统出现死锁时一定同时保持了四个必要条件,对资源采用按序分配算法后可破坏的条件是(A)。 A、循环等待条件B、互斥条件C、占有并等待条件D、不可抢占条件 2、资源的静态分配算法在解决死锁问题中是用于(B)。 …...
Django自动生成docs接口文档
1.创建Django项目 python manage.py startproject django20252.创建子应用 python manage.py startapp api3.安装依赖包 pip install coreapi4.创建urls.py from django.contrib import admin from django.urls import path, include from rest_framework import routers f…...
Mock 数据
1. Mock 数据的方式 2. json-server 实现 Mock 数据 项目中安装json-server npm i -D json-server准备一个json文件添加启动命令 //package.json"scripts": {"start": "craco start","build": "craco build","test&q…...
(三)C语言之for语句概述
(三)C语言之for语句概述 一、使用for语句实现打印华氏温度与摄氏温度转换二、for语句概述三、练习 一、使用for语句实现打印华氏温度与摄氏温度转换 #include <stdio.h> /*当华氏温度为 0,20,40,...300时,打印出华氏温度与摄氏温度对照…...
OpenLDAP配置web管理界面PhpLDAPAdmin服务-centos9stream
之前已经发了一篇关于centos9下面配置openldap多主高可用集群的内容,不会配置ldap集群的请参考:服务器集群配置LDAP统一认证高可用集群(配置tsl安全链接)-centos9stream-openldap2.6.2-CSDN博客 这里跟着前篇文章详细说明如何配置…...
深兰科技多款大模型技术产品登上新闻联播!
11月20日晚,新闻联播报道了2023中国5G工业互联网大会,深兰科技metamind、汉境大型城市智能体空间等大模型技术和产品在众多参展产品中脱颖而出,被重点播报。 2023中国5G工业互联网大会 本届大会由工信部和湖北省人民政府联合主办,…...
移远通信推出六款新型天线,为物联网客户带来更丰富的产品选择
近日,移远通信重磅推出六款新型天线,覆盖5G、非地面网络(NTN)等多种新技术,将为物联网终端等产品带来全新功能和更强大的连接性能。 移远通信COO张栋表示:“当前,物联网应用除了需要高性能的天线…...
八、ffmpeg录制视频为yuv文件
前言 测试环境: ffmpeg的4.3.2自行编译版本windows环境qt5.12 图片的一些重要知识: RGB图片 位深度:每一个像素都会使用n个二进制位来存储颜色信息。每一个像素的颜色都是由红(Red)、绿(Green࿰…...
Rust并发编程:理解线程与并发
大家好!我是lincyang。 今天我们来深入探讨Rust中的并发编程,特别是线程的使用和并发的基本概念。 Rust中的线程 Rust使用线程来实现并发。线程是操作系统可以同时运行的最小指令集。在Rust中,创建线程非常简单,但与此同时&…...
二次开发问题汇总【C#】
1未将对象引用到实例。 接口函数的参数不对。解决办法【用fixed去限制数组长度】 unsafe public struct VCI_BOARD_INFO {public UInt16 hw_Version;public UInt16 fw_Version;public UInt16 dr_Version;public UInt16 in_Version;public UInt16 irq_Num;public byte can_Num;…...
中职组网络安全B模块-渗透提权2
任务五:渗透提权2 任务环境说明: 仅能获取xxx的IP地址 用户名:test,密码:...
上市公司会计审计报告5种意见的含义,看完秒懂
上市公司会计审计报告5种意见的含义,看完秒懂 关键词:审计报告类型、无保留意见、保留意见、否定意见、无法表示意见、财务审计科普表1-1 会计师出具意见与其真实意思对照会计师出具意见会计师真实意思标准无保留意见的审计报告造假迹象未被本人发现附带…...
【Linux驱动开发】第一天:用户态与内核态通俗讲解+最简字符设备驱动实战
一、通俗类比:把Linux系统比作国际机场 快速建立认知,秒懂底层权限模型:计算机系统国际机场 类比硬件资源(CPU、内存、硬盘、外设)机场跑道、设施、物资、场地Linux 内核机场管理局空管工作人员用户态应用(…...
群体神经网络:分布式API调用与弹性计算新范式
1. 项目概述:群体神经网络如何重构函数与API调用 在传统分布式计算中,函数调用和API执行往往受限于单一节点的处理能力与可靠性。三年前我在构建一个高并发交易系统时,就曾因单个API节点崩溃导致整个服务雪崩。而群体神经网络(Swa…...
从计算sin(π/6)开始:手把手教你用STM32的DSP库做实际信号处理
从计算sin(π/6)到实时频谱分析:STM32 DSP库实战指南 在嵌入式开发中,信号处理一直是提升系统性能的关键环节。想象一下,你正在设计一个智能家居的声控模块,需要快速识别用户的语音指令;或者开发一款工业设备的状态监测…...
NSH-12RH齿轮电机
Bodine Electric NSH-12RH是并励式直流齿轮电机,适用于需要稳定转速和调节特性的工业传动应用。电压等级115V DC,电流0.33A,功率1/50HP。采用并励绕组结构,磁场由独立励磁绕组产生。转速特性较硬,负载变化时转速波动小…...
将军思维:在亚马逊,为何“关注对手”比“优化自己”重要一百倍
亚马逊的运营者可分为两种:“自我导向”型与“他人导向”型。这两种思维模式,将直接决定你的品牌是在内部的自嗨中慢性死亡,还是在外部的心智战场上攻城略地。 “自我导向”型运营者无法理解定位时代的本质: 你的产品定位&…...
从零到上线:用Visual Studio 2022和IIS Manager完整部署.NET 8.0 MVC应用
从零到上线:用Visual Studio 2022和IIS Manager完整部署.NET 8.0 MVC应用 对于刚接触.NET开发的初学者来说,将第一个MVC应用成功部署到生产环境可能是个令人望而生畏的任务。本文将带你走过从项目创建到最终发布的完整旅程,特别针对.NET 8.0和…...
MySQL性能优化:深入理解索引原理与查询优化实战
作为一名后端开发,MySQL是绕不开的必修课。在日常工作中,慢查询往往是系统性能的头号杀手,而索引则是解决这一问题的核心利器。本文将带你从索引的本质出发,深入B树原理,结合Explain工具分析慢SQL,并总结一…...
maven常用命令大全
参考地址: 1.maven常用命令大全(附详细解释),https://blog.csdn.net/good_good_xiu/article/details/116740333 2.maven常用命令集合(收藏大全),https://zhuanlan.zhihu.com/p/355889432 3.Maven查看插件信息&#…...
3分钟快速上手:B站m4s视频转换MP4完整教程
3分钟快速上手:B站m4s视频转换MP4完整教程 【免费下载链接】m4s-converter 一个跨平台小工具,将bilibili缓存的m4s格式音视频文件合并成mp4 项目地址: https://gitcode.com/gh_mirrors/m4/m4s-converter 核心关键词:m4s转MP4 长尾关键…...
