一个注解实现分布式锁加锁

目录

一、概述

二、代码的实现

1、引入依赖

2、配置Redisson

3、定义注解

4、添加aop的切面方法

5、 支持 SpEL 表达式

三、代码验证

四、总结


一、概述

     在微服务项目的开发进程中,分布式锁的应用场景屡见不鲜。此时,我们需要借助分布式组件来实现,常见的分布式组件包括 Redis、Zookeeper、Etcd 等。然而,结合实际业务状况分析,通常会优先选择 Redis,原因在于 Redis 往往是微服务系统的必备组件,无需另行搭建。而在其中,我们常常运用基于 Redis 实现的 Java 分布式对象存储和缓存框架 Redisson 来达成分布式锁的功能。

        在通过 Redisson 实现分布式锁时,我们都得编写如下代码。但这样一来,每次使用都要书写这些代码,不仅麻烦(主要是因为懒),代码重复率高,而且加分布式锁的代码和业务代码相互耦合。鉴于此,我采用了 Spring 中的注解与 AOP 方式,以实现代码复用,进而简化分布式锁的加锁与解锁流程。

public void process() {RLock lock = redissonClient.getKey(key);try{if(lock.tryLock()){//执行自己的业务逻辑}} finally {if(lock.isHeldByCurrentThread()){lock.unlock();}}
}

        优化之后,代码的调用方式如下,这极大地简化了分布式锁的使用。

@DistributedLock(key = "process")
public void process() {// 具体的业务逻辑
}

二、代码的实现

1、引入依赖

        首先,我们需要导入以下依赖:

<dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-redis</artifactId><version>1.8.14.RELEASE</version>
</dependency>
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>2.15.2</version>
</dependency>


2、配置Redisson

      新建一个 RedissonConfig.java 文件,代码如下:

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class RedissonConfig {@Beanpublic RedissonClient redissonClient() {Config config = new Config();config.useSingleServer()//这里配置你redis的地址.setAddress("redis://127.0.0.1:6379");// 如果有密码.setPassword("xxxx");.setDatabase(0).setConnectionPoolSize(10).setConnectionMinimumIdleSize(2);return Redisson.create(config);}
}


3、定义注解

首先,我们定义一个注解 @DistributedLock ,该注解包含以下几个参数 :

  • key:redis 锁的键,支持 SpEL 表达式。
  • waitTime:等待时间,默认为 0 毫秒。
  • expireTime:过期秒数,默认为 -1,使用 watchDog。
  • timeUnit:超时时间单位。
  • errorMsg:报错信息。

4、添加aop的切面方法

        在 around() 方法中,参数 joinPoint 是切入点,distributedLock 是切入点形参,用于传入键。这里,我们的键使用的是 SpEL 表达式,通过 SpelExpressionParser 能够获取最终的键值,joinPoint.proceed() 执行的则是上文提及的原方法的执行内容。

import cn.hutool.core.util.StrUtil;
import com.fhey.common.annotation.DistributedLock;
import com.fhey.common.exception.BusinessException;
import com.fhey.common.utils.SpelUtil;
import lombok.extern.slf4j.Slf4j;
import net.logstash.logback.encoder.org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;/*** @author fhey* @date 2023-01-02 13:53:39* @description: 分布式锁注解切面*/
@Aspect
@Component
@Slf4j
public class DistributeLockAspect {@Autowiredprivate RedissonClient redissonClient;private static final String REDISSON_LOCK_PREFIX = "fhey:lock:";public static final String DEFAULT_EXPRESSION_PREFIX = "#";@Around("@annotation(distributedLock)")public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {Object[] args = joinPoint.getArgs();if (distributedLock == null || redissonClient == null) {return joinPoint.proceed(args);}String key = distributedLock.key();String errorMsg = distributedLock.errorMsg();String lockKey = getLockKey(joinPoint, key);try {boolean tryLock = tryLock(lockKey, distributedLock.waitTime(), distributedLock.expireTime(), distributedLock.timeUnit());if (!tryLock){log.info("distributed lock fail, key: {}",lockKey);if(StringUtils.isNotBlank(errorMsg)){throw new BusinessException(errorMsg);}return null;}return joinPoint.proceed(args);} finally {unlock(lockKey);}}private boolean tryLock(String lockKey, int waitTime, int expireTime, TimeUnit timeUnit) {try {RLock lock = redissonClient.getLock(lockKey);boolean res = lock.tryLock(waitTime, expireTime, timeUnit);log.debug("distributed lock state:{}, lockKey:{}", res, lockKey);return res;} catch (Exception e) {log.error("distributed lock err,lockKey:{}", lockKey, e);throw new RuntimeException(e);}}private void unlock(String lockKey) {try {RLock lock = redissonClient.getLock(lockKey);lock.unlock();log.debug("UnLock distributed lock successfully, lockKey:{}", lockKey);} catch (Exception e) {log.error("Can not unlock, lockKey:{}", lockKey, e);}}/*** 将spel表达式转换为字符串* @param joinPoint 切点* @return redisKey*/private String getLockKey(ProceedingJoinPoint joinPoint,String key) {String lockKey;if (!key.contains(DEFAULT_EXPRESSION_PREFIX)) {lockKey = key;} else {Signature signature = joinPoint.getSignature();MethodSignature methodSignature = (MethodSignature) signature;Method targetMethod = methodSignature.getMethod();Object target = joinPoint.getTarget();Object[] arguments = joinPoint.getArgs();lockKey = SpelUtil.parse(target,key, targetMethod, arguments);}lockKey = REDISSON_LOCK_PREFIX  + StrUtil.COLON + lockKey;return lockKey;}
}

5、 支持 SpEL 表达式

        为了使 key 字段能够支持 SpEL 表达式,所以在 getLockKey 方法中进行了 SpEL 解析,解析的工具类方法如下:

package com.fhey.common.utils;import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.lang.reflect.Method;/*** @author fhey* @date 2023-01-02 13:21:43* @description: Spel解析工具类*/
public class SpelUtil {/*** 支持 #p0 参数索引的表达式解析* @param rootObject 根对象,method 所在的对象* @param spel 表达式* @param method ,目标方法* @param args 方法入参* @return 解析后的字符串*/public static String parse(Object rootObject, String spel, Method method, Object[] args) {if (StrUtil.isBlank(spel)) {return StrUtil.EMPTY;}//获取被拦截方法参数名列表(使用Spring支持类库)LocalVariableTableParameterNameDiscoverer u =new LocalVariableTableParameterNameDiscoverer();String[] paraNameArr = u.getParameterNames(method);if (ArrayUtil.isEmpty(paraNameArr)) {return spel;}//使用SPEL进行key的解析ExpressionParser parser = new SpelExpressionParser();//SPEL上下文StandardEvaluationContext context = new MethodBasedEvaluationContext(rootObject,method,args,u);//把方法参数放入SPEL上下文中for (int i = 0; i < paraNameArr.length; i++) {context.setVariable(paraNameArr[i], args[i]);}return parser.parseExpression(spel).getValue(context, String.class);}
}

三、代码验证

package com.fhey.common.utils;import com.fhey.common.annotation.DistributedLock;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;@SpringBootTest
public class DistributedLockTest {@Resourceprivate TestServiceWithDistributedLock testService;  // 注入使用分布式锁的服务@Testpublic void testDistributedLock() {// 模拟并发调用服务方法Thread thread1 = new Thread(() -> {testService.processWithLock("key1");});Thread thread2 = new Thread(() -> {testService.processWithLock("key1");});thread1.start();thread2.start();try {thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}}class TestServiceWithDistributedLock {@DistributedLock(key = "#key1",errorMsg = "操作失败,请稍后重试!")public void processWithLock(String key) {// 模拟关键业务逻辑执行System.out.println("获取key: " + key + " 成功!");try {Thread.sleep(5000);  // 模拟耗时操作} catch (InterruptedException e) {e.printStackTrace();}}}
}

执行结果:

获取key: key1 成功!
操作失败,请稍后重试!

四、总结

        本文详细介绍了在微服务项目开发中使用分布式锁的优化方法。首先阐述了常见分布式组件及选择 Redis 与 Redisson 的原因接着重点说明了通过 Spring 注解加 AOP 方式实现代码复用、简化分布式锁加锁和解锁流程的具体步骤,包括引入依赖、配置 Redisson、定义注解、添加 AOP 切面方法、支持 SpEL 表达式等,并提供了相应的代码示例和解释,为开发者在处理分布式锁相关问题时提供了有效的参考和解决方案。

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

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

相关文章

240723基于opencv下图像阈值

文章目录 1.实验环境2.实验目的3.实验代码4.实验结果1.实验环境 python=3.6 opencv=3.4.1 编译器pycharm 2.实验目的 学习数字图像处理中关于阈值处理的几种方式,分析其中的临界值以及他们的区别 3.实验代码 # @File: 15.1简单阈值.py # @Author: chen_song # @Time: 202…

论文阅读——Integrated Diffusive Antenna Array of Low Backscattering

文章目录 摘要一、背景介绍二、天线结构A. 缝隙天线B. 低频扩散单元C. 高频扩散单元D. 集成设计 三、验证总结 论文来源&#xff1a;https://ieeexplore.ieee.org/document/10309141 摘要 文章提出了一种低雷达散射截面&#xff08;RCS&#xff09;的扩散天线阵列。 作为示例…

优化PyCharm:让IDE响应速度飞起来

优化PyCharm&#xff1a;让IDE响应速度飞起来 PyCharm&#xff0c;作为一款功能强大的集成开发环境&#xff08;IDE&#xff09;&#xff0c;在提供丰富功能的同时&#xff0c;有时也会出现响应慢的问题。这不仅影响开发效率&#xff0c;还可能打击开发者的积极性。本文将详细…

Linux内存管理--系列文章八——内存管理架构

一、引子 上篇文章讲述了目前内存的硬件架构&#xff0c;本篇阐述内核中是怎么表示不同架构的物理内存页。 二、平坦内存模型&#xff08;Flat Memory Model&#xff09; 在该模型下&#xff0c;物理内存是连续的&#xff0c;所以物理地址也是连续的。这时内核使用struct pa…

STM32嵌入式人工智能边缘计算应用教程

目录 引言环境准备边缘计算系统基础代码实现&#xff1a;实现嵌入式人工智能边缘计算系统 4.1 数据采集模块 4.2 数据处理与推理模块 4.3 通信与网络系统实现 4.4 用户界面与数据可视化应用场景&#xff1a;边缘计算与优化问题解决方案与优化收尾与总结 1. 引言 嵌入式人工智…

前后端分离项目部署,vue--nagix发布部署,.net--API发布部署。

目录 Nginx免安装部署文件包准备一、vue前端部署1、修改http.js2、npm run build 编译项目3、解压Nginx免安装,修改nginx.conf二、.net后端发布部署1、编辑appsetting.json,配置跨域请求2、配置WebApi,点击发布3、配置文件发布到那个文件夹4、配置发布相关选项5、点击保存,…

Python-numpy基础--------2

1.full()创建函数 目录 1.full()创建函数 2.创建单位矩阵 3.linspace创建 4.logspace 创建 5.二维数组的索引和切片&#xff1a; 1.索引直接获取 在NumPy中&#xff0c;full() 函数用于创建一个给定形状、类型的新数组&#xff0c;并用指定的值填充这个数组。这个函数非…

【vue前端项目实战案例】Vue3仿今日头条App

本文将开发一款仿“今日头条”的新闻App。该案例是基于 Vue3.0 Vue Router webpack TypeScript 等技术栈实现的一款新闻资讯类App&#xff0c;适合有一定Vue框架使用经验的开发者进行学习。 项目源码在文章末尾 1 项目概述 该项目是一款“今日头条”的新闻资讯App&#xf…

go语言day14 bufio包 ioutil包

Golang-100-Days/Day16-20(Go语言基础进阶)/day16_file操作.md at master rubyhan1314/Golang-100-Days GitHub 一、bufio包 读写文件 1&#xff09; bufio包下的Reader类实现了Read()方法和Write()方法 2&#xff09;和io包相比&#xff0c;虽然都是在读写文件&#xff0c;…

【Android】性能实践—编码优化与布局优化学习笔记

【Android】性能实践—编码优化与布局优化学习笔记 编码优化 使用场景 如果需要拼接字符串&#xff0c;优先使用StringBuffer和StringBuilder进行凭借&#xff0c;他们的性能优于直接用加号进行拼接&#xff0c;因为使用加号连接符会创建多余的对象一般情况下使用基本数据类…

scrapy生成爬虫数据为excel

scrapy生成爬虫数据为excel 使用openpyxl&#xff08;推荐&#xff09;安装openpyxl库建一个新的Item Pipeline类在settings.py中启用ExcelPipeline说明 使用scrapy-xlsx首先&#xff0c;安装scrapy-xlsx&#xff1a;然后在Scrapy爬虫中使用管道&#xff1a;说明 要使用Scrapy生…

Unity扩展SVN命令

可以直接在unity里右键文件提交和查看提交记录 顶部菜单栏上回退和更新整个unity工程 SvnForUnity.CS 记得要放在Editor文件夹下 using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using UnityEditor; using Unity…

Era3D 论文学习

代码地址&#xff1a;https://penghtyx.github.io/Era3D/ 论文地址&#xff1a;https://arxiv.org/abs/2405.11616 解决了什么问题&#xff1f; 尽管多视角内容生成领域取得了显著的进展&#xff0c;但现有的方案仍然面临着相机先验不匹配、效果差、分辨率低等问题&#xff0…

Python 数据爬取理论解读

在信息化时代&#xff0c;数据是最宝贵的资源之一。很多企业和个人都希望能够获取大量的数据来分析趋势、了解市场、预测未来等。Python 作为一门强大的编程语言&#xff0c;其简洁的语法和丰富的库使得数据爬取变得相对容易。本文将从程序员的角度出发&#xff0c;详细探讨 Py…

windows USB 设备驱动开发- 对 MUTT 设备进行 BIOS/UEFI 测试

对 MUTT 设备进行 BIOS/UEFI 测试&#xff0c;BIOS/UEFI 测试验证 USB 启动以及控制器到操作系统的切换。 USB 启动配置 在 USB 2.0 (EHCI) 和 USB 3.0 (xHCI) 控制器上执行这些测试&#xff0c;每种主要 USB 媒体类型 &#xff0c;USB 2.0 BOT、USB 3.0 BOT 和 USB 3.0 UASP…

Oracle物化视图解析

目录 一、物化视图的优点二、物化视图的缺点三、物化视图的类型四、创建物化视图五、刷新物化视图1、手动刷新2、自动刷新&#xff08;1&#xff09;刷新策略&#xff08;2&#xff09;定期刷新&#xff08;3&#xff09;快速刷新3.1、快速刷新过程3.2、快速刷新的优点3.3、使用…

pytorch中的面向对象编程方法

一、__xxx__形式的魔法方法 我们可以经常在python代码片段中看到类的定义&#xff0c;其中第一个被定义的方法往往是__init__&#xff0c;如下所示&#xff1a; class Accumulator: """在n个变量上累加"""def __init__(self, n):self.data […

【Android】ListView和RecyclerView知识总结

文章目录 ListView步骤适配器AdpterArrayAdapterSimpleAdapterBaseAdpter效率问题 RecyclerView具体实现不同布局形式的设置横向滚动瀑布流网格 点击事件 ListView ListView 是 Android 中的一种视图组件&#xff0c;用于显示可滚动的垂直列表。每个列表项都是一个视图对象&…

【JavaScript】前端路由

前端路由是指在前端⻚⾯内部实现⻚⾯之间的跳转&#xff0c;⽽不是像传统的⽹⻚跳转那样在后端进⾏⻚⾯跳转&#xff0c;从后端获取 html 页面。前端路由使⽤浏览器的 history 接⼝&#xff0c;通过改变浏览器的 URL&#xff0c;来更新⻚⾯的视图。 前端路由适合⽤于单⻚⾯应⽤…

Python教程(一):环境搭建及PyCharm安装

目录 引言1. Python简介1.1 编译型语言 VS 解释型语言 2. Python的独特之处3. Python应用全览4. Python版本及区别5. 环境搭建5.1 安装Python&#xff1a; 6. 开发工具&#xff08;IDE&#xff09;6.1 PyCharm安装教程6.2 永久使用教程 7. 编写第一个Hello World结语 引言 在当…