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

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

目录

一、概述

二、代码的实现

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 表达式等,并提供了相应的代码示例和解释,为开发者在处理分布式锁相关问题时提供了有效的参考和解决方案。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • RockyLinux 9 PXE Server bios+uefi 自动化部署 RockLinux 8 9
  • 数据库编程中游标 连接 commit
  • js——浅拷贝和深拷贝
  • 【Git多人协作开发】同一分支下的多人协作开发模式
  • springboot配置文件如何读取pom.xml的值
  • 新电脑如何设置 npm 源及查看源、安装 cnpm、pnpm 和 yarn 的详细教程
  • Python研究生毕业设计,数据挖掘、情感分析、机器学习
  • scikit-learn中fit_transform会改变原始数据吗
  • 江科大/江协科技 STM32学习笔记P9-11
  • Si24R03:高度集成的低功耗SOC芯片中文资料
  • 05 ES6中的Set类型
  • openssl req 详解
  • c++——map和set
  • 解决vscode+UE5中vscode无法识别头文件,无法函数无法跳转,也无法自动补全的问题。
  • 科研绘图系列:R语言和弦图 (Chord diagram)
  • [NodeJS] 关于Buffer
  • 08.Android之View事件问题
  • 230. Kth Smallest Element in a BST
  • Angular Elements 及其运作原理
  • ComponentOne 2017 V2版本正式发布
  • Create React App 使用
  • Docker入门(二) - Dockerfile
  • go语言学习初探(一)
  • Java比较器对数组,集合排序
  • java多线程
  • java正则表式的使用
  • JS基础之数据类型、对象、原型、原型链、继承
  • Js基础知识(四) - js运行原理与机制
  • Making An Indicator With Pure CSS
  • MYSQL如何对数据进行自动化升级--以如果某数据表存在并且某字段不存在时则执行更新操作为例...
  • niucms就是以城市为分割单位,在上面 小区/乡村/同城论坛+58+团购
  • python大佬养成计划----difflib模块
  • Spring Security中异常上抛机制及对于转型处理的一些感悟
  • vue从入门到进阶:计算属性computed与侦听器watch(三)
  • 基于Javascript, Springboot的管理系统报表查询页面代码设计
  • 基于MaxCompute打造轻盈的人人车移动端数据平台
  • 离散点最小(凸)包围边界查找
  • 浅谈web中前端模板引擎的使用
  • 数据可视化之 Sankey 桑基图的实现
  • 译米田引理
  • 优秀架构师必须掌握的架构思维
  • ​猴子吃桃问题:每天都吃了前一天剩下的一半多一个。
  • #使用清华镜像源 安装/更新 指定版本tensorflow
  • $.each()与$(selector).each()
  • (02)Cartographer源码无死角解析-(03) 新数据运行与地图保存、加载地图启动仅定位模式
  • (17)Hive ——MR任务的map与reduce个数由什么决定?
  • (173)FPGA约束:单周期时序分析或默认时序分析
  • (3)nginx 配置(nginx.conf)
  • (arch)linux 转换文件编码格式
  • (LeetCode 49)Anagrams
  • (Python) SOAP Web Service (HTTP POST)
  • (PyTorch)TCN和RNN/LSTM/GRU结合实现时间序列预测
  • (Redis使用系列) Springboot 使用redis实现接口Api限流 十
  • (二)c52学习之旅-简单了解单片机
  • (二)丶RabbitMQ的六大核心