SpringBoot-Starter 自动锁组件

在日常业务开发的过程中,我们经常会遇到存在高并发的场景,这个时候都会选择使用redis来实现一个锁,来防止并发。

但是很多时候,我们可能业务完成后,就需要把锁释放掉,给下一个线程用,但是如果我们忘记了释放锁,可能就会存在死锁的问题。(对于使用锁不太熟练的话,这种情况时常发生,虽然很多时候,我们的锁是有过期时间的,但是如果忘记了释放,那么在这个过期时间内,还是会存在大的损失)。

还有一点就是,在我们使用redis实现一个锁的时候,我们需要导入redisClient,设置key,设置过期时间,设置是否锁等等一些重复的操作。前面的哪些步骤,很多都是重复的,所以我们可以想一个方法,来把重复的东西都抽象出来,做成统一的处理,同时哪些变化的值,提供一个设置的入口。

抽出来的东西,我们还可以封装成一个spring-boot-stater,这样我们只需要写一份,就可以在不同的项目中使用了。说干就干,下面我们使用redisson,完成一个自动锁的starter

实现

首先,我们分析一下哪些东西是我们需要进行合并,哪些又是需要提供给使用方的。得到下面的一些问题

  • 加锁、释放锁过程 我们需要合并起来

  • 锁key,加锁时间......这些需要给使用方注入

  • 锁的key该怎么去生成(很多时候,我们需要根据业务字段去构造一个key,比如 user:{userId}),那么这个userId该怎么获取?

我们从上面需要解决的问题,去思考需要怎么去实现。我们需要封装一些公共的逻辑,又需要提供一些配置的入库,这样的话,我们可以尝试一种方法,使用 注解+AOP,通过注解的方式完成加锁、解锁。(很多时候,如果需要抽出一些公共的方法,会用到注解+AOP去实现)

定义注解

AutoLock 注解

一个锁需要有的信息有,key,加锁的时间,时间单位,是否尝试加锁,加锁等待时间 等等。(如果还有其他的业务需要,可以添加一个扩展内容,自己去解析处理) 那么这个注解的属性就可以知道有哪些了

/*** 锁的基本信息*/
@Target({ElementType.METHOD})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoLock {/*** 锁前缀*/String prefix() default "anoxia:lock";/*** 加锁时间*/long lockTime() default 30;/*** 是否尝试加锁*/boolean tryLock() default true;/*** 等待时间,-1 不等待*/long waitTime() default -1;/*** 锁时间类型*/TimeUnit timeUnit() default TimeUnit.MILLISECONDS;}
LockField 注解

这个注解添加到参数属性上面,用来解决上面提到获取不同的业务参数内容构造key的问题。所以我们需要提供一个获取哪些字段来构造这个key配置,这里需要考虑两个问题:

  • 1、参数是基本类型

  • 2、参数是引用类型 - 这种类型需要从对象中拿到对象的属性值

/*** 构建锁的业务数据* @author huangle* @date 2023/5/5 15:01*/
@Target({ElementType.PARAMETER})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface LockField {String[] fieldNames() default {};}

定义切面

重点就在这个切面里面,我们需要在这里完成key的合成,锁的获取与释放。整个过程可以分为以下几步

  • 获取锁的基本信息,构建key

  • 加锁,执行业务

  • 业务完成,释放锁

/*** 自动锁切面* 处理加锁解锁逻辑** @author huangle* @date 2023/5/5 14:50*/
@Aspect
@Component
public class AutoLockAspect {private final static Logger LOGGER = LoggerFactory.getLogger(AutoLockAspect.class);@Resourceprivate RedissonClient redissonClient;private static final String REDIS_LOCK_PREFIX = "anoxiaLock";private static final String SEPARATOR = ":";/*** 定义切点*/@Pointcut("@annotation(cn.anoxia.lock.annotation.AutoLock)")public void lockPoincut() {}/*** 定义拦截处理方式** @return*/@Around("lockPoincut()")public Object doLock(ProceedingJoinPoint joinPoint) throws Throwable {// 获取需要加锁的方法MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();Method method = methodSignature.getMethod();// 获取锁注解AutoLock autoLock = method.getAnnotation(AutoLock.class);// 获取锁前缀String prefix = autoLock.prefix();// 获取方法参数Parameter[] parameters = method.getParameters();StringBuilder lockKeyStr = new StringBuilder(prefix);Object[] args = joinPoint.getArgs();// 遍历参数int index = -1;LockField lockField;// 构建keyfor (Parameter parameter : parameters) {Object arg = args[++index];lockField = parameter.getAnnotation(LockField.class);if (lockField == null) {continue;}String[] fieldNames = lockField.fieldNames();if (fieldNames == null || fieldNames.length == 0) {lockKeyStr.append(SEPARATOR).append(arg);} else {List<Object> filedValues = ReflectionUtil.getFiledValues(parameter.getType(), arg, fieldNames);for (Object value : filedValues) {lockKeyStr.append(SEPARATOR).append(value);}}}String lockKey = REDIS_LOCK_PREFIX + SEPARATOR + lockKeyStr;RLock lock = redissonClient.getLock(lockKey);// 加锁标志位boolean lockFlag = false;try {long lockTime = autoLock.lockTime();long waitTime = autoLock.waitTime();TimeUnit timeUnit = autoLock.timeUnit();boolean tryLock = autoLock.tryLock();try {if (tryLock) {lockFlag = lock.tryLock(waitTime, lockTime, timeUnit);} else {lock.lock(lockTime, timeUnit);lockFlag = true;}}catch (Exception e){LOGGER.error("加锁失败!,错误信息", e);throw new RuntimeException("加锁失败!");}if (!lockFlag) {throw new RuntimeException("加锁失败!");}// 执行业务return joinPoint.proceed();} finally {// 释放锁if (lockFlag) {lock.unlock();LOGGER.info("释放锁完成,key:{}",lockKey);}}}}
获取业务属性

这个是一个获取对象中字段的工具类,在一些常用的工具类里面也有实现,可以直接使用也可以自己实现一个

/*** @author huangle* @date 2023/5/5 15:17*/
public class ReflectionUtil {public static List<Object> getFiledValues(Class<?> type, Object target, String[] fieldNames) throws IllegalAccessException {List<Field> fields = getFields(type, fieldNames);List<Object> valueList = new ArrayList();Iterator fieldIterator = fields.iterator();while(fieldIterator.hasNext()) {Field field = (Field)fieldIterator.next();if (!field.isAccessible()) {field.setAccessible(true);}Object value = field.get(target);valueList.add(value);}return valueList;}public static List<Field> getFields(Class<?> claszz, String[] fieldNames) {if (fieldNames != null && fieldNames.length != 0) {List<String> needFieldList = Arrays.asList(fieldNames);List<Field> matchFieldList = new ArrayList();List<Field> fields = getAllField(claszz);Iterator fieldIterator = fields.iterator();while(fieldIterator.hasNext()) {Field field = (Field)fieldIterator.next();if (needFieldList.contains(field.getName())) {matchFieldList.add(field);}}return matchFieldList;} else {return Collections.EMPTY_LIST;}}public static List<Field> getAllField(Class<?> claszz) {if (claszz == null) {return Collections.EMPTY_LIST;} else {List<Field> list = new ArrayList();do {Field[] array = claszz.getDeclaredFields();list.addAll(Arrays.asList(array));claszz = claszz.getSuperclass();} while(claszz != null && claszz != Object.class);return list;}}
}

配置自动注入

在我们使用 starter 的时候,都是通过这种方式,来告诉spring在加载的时候,完成这个bean的初始化。这个过程基本是定死的。就是编写配置类,如果通过springBoot的EnableAutoConfiguration来完成注入。注入后,我们就可以直接去使用这个封装好的锁了。

/*** @author huangle* @date 2023/5/5 14:50*/
@Configuration
public class LockAutoConfig {@Beanpublic AutoLockAspect autoLockAspect(){return new AutoLockAspect();}}// spring.factories 中内容
org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.anoxia.lock.config.LockAutoConfig

测试

我们先打包这个sarter,然后导入到一个项目里面(打包导入的过程就不说了,自己去看一下就可以) 直接上测试类,下面执行后可以看到锁已经完成了释放。如果业务抛出异常导致中断也不用担心锁不会释放的问题,因为我们是在 finally 中释放锁的

/*** @author huangle* @date 2023/5/5 14:28*/
@RestController
@RequestMapping("/v1/user")
public class UserController {@AutoLock(lockTime = 3, timeUnit = TimeUnit.MINUTES)@GetMapping("/getUser")public String getUser(@RequestParam @LockField String name) {return "hello:"+name;}@PostMapping("/userInfo")@AutoLock(lockTime = 1, timeUnit = TimeUnit.MINUTES)public String userInfo(@RequestBody @LockField(fieldNames = {"id", "name"}) UserDto userDto){return userDto.getId()+":"+userDto.getName();}}

图片

图片

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

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

相关文章

Java 中泛型的基本使用

目录 一、泛型类的使用 二、泛型接口的使用 三、泛型方法的使用 相关测试 一、泛型类的使用 /* 泛型类&#xff0c;T 表示 Java 中的任意类型&#xff0c;也就是说构造方法中 data 属性可以传递任意类型的值*/ class ResultData<T>{Integer code;String msg;T data;p…

客户用IE批量下载文件文件到不同文件夹不压缩(包含分页带条件动态查询)

<!DOCTYPE html> <html lang"en" xmlns:th"http://www.thymeleaf.org"> <head><meta charset"UTF-8"><title>首页</title><script type"text/javascript" th:src"{/vue/jQuery.js}"…

免费scrum管理工具Leangoo敏捷做缺陷跟踪管理

缺陷管理通常关注如下几个方面&#xff1a; 1. 缺陷的处理速度 2. 缺陷处理的状态 3. 缺陷的分布 4. 缺陷产生的原因 使用Leangoo敏捷看板我们可以对缺陷进行可视化的管理&#xff0c;方便我们对缺陷的处理进展、负责人、当前状态、分布情况等各个方面一目了然。 下面我们…

在less中选择某个父元素的儿子的办法

在 LESS 中> 选择器&#xff0c;选择的是 儿子元素&#xff0c;就是 必须与父元素 有直接血源的元素。例如 /deep/ .dialog-footer > .el-button

安卓APP和小程序渗透测试技巧总结

记得开启模拟器的ROOT权限&#xff1a;本文章仅供学习和研究使用&#xff0c;严禁使用该文章内容对互联网其他应用进行非法操作&#xff0c;若将其用于非法目的&#xff0c;所造成的后果由您自行承担。 由于安卓7开始对系统安全性做了些改动&#xff0c;导致应用程序不再信任客…

设计模式——一文即可

对常用设计模式的总结&#xff0c;也是对设计模式专栏的总结 简单工厂模式 简单工厂模式&#xff08;Simple Factory Pattern&#xff09;是一种创建型设计模式&#xff0c;它提供了一种创建对象的最佳方式&#xff0c;通过将对象的创建逻辑封装在一个工厂类中&#xff0c;客…

【深度学习环境搭建】Windows搭建Anaconda3、已经Pytorch的GPU版本

目录 搭建Anaconda3搭建GPU版本的Pytorch你的pip也要换源&#xff0c;推荐阿里源打开conda的PowerShell验证 搭建Anaconda3 无脑下载安装包安装&#xff08;自行百度&#xff09; 注意点&#xff1a; 1、用户目录下的.condarc需要配置&#xff08;自定义环境的地址&#xff08…

1004:最大连续1的个数 III

1004&#xff1a;最大连续1的个数 III 1004&#xff1a;最大连续1的个数 III 给定一个二进制数组 nums 和一个整数 k&#xff0c;如果可以翻转最多 k 个 0 &#xff0c;则返回 数组中连续 1 的最大个数 。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,1,0,0,0,1,1,1,1,…

QT quick基础:加载资源文件(字体)

一、加载字体 1、准备字体库 Roboto-Regular.ttf 2、在工程下面新建文件夹fonts&#xff0c;并将字体库放到该文件夹下面。 3、在QT Create 工程中添加字体。 添加现有文件选择Roboto-Regular.ttf。 4、执行qmake 5、在.qml文件加载字体 /* 加载字体 */FontLoader {id: f…

Java8 Stream笔记

什么是Stream&#xff1f; Java 8 中的 Stream 是对数组和集合&#xff08;Collection&#xff09;对象功能的增强&#xff0c; 专注于对集合对象进行各种非常便利、高效的聚合操作&#xff0c;或者大批量数据操作。 Stream的特性&#xff1f; ➢ 不存储数据。Stream 是基于…

Linux批量快速修改文件名的三种方法

在Linux中&#xff0c;批量重命名文件是一项常见且有用的操作。以下是三种常用的批量重命名文件的方法&#xff0c;每种方法都附有示例。这些方法既可以适用于新手&#xff0c;也适用于更有经验的用户。 话不多说&#xff0c;直接上干货&#xff01; rename 命令 rename命令是…

浅谈电动汽车充电站箱变电气安全物联监测系统设计与应用

摘 要:基于物联网技术架构提出了一种适用于电动汽车充电站箱变的电气安全物联监测系统设计方案。该系统由电气安全智能感知设备、通信网关、电气安全物联网监测平台等构成&#xff0c;可支持充电站箱变充电桩出线回路电流、电缆 温度、剩余电流、故障电弧、短路电流等数据采集监…

Swagger的使用

一、引入Maven坐标 <dependency><groupld>com.github.xiaoymin</groupld><artifactld>knife4j-spring-boot-starter</artifactld><version>3.0.2</version> </dependency>二、配置类中添加配置 /*** 通过knife4j生成接口文档…

PagedAttention: from interface to kernal

1 Overview PagedAttention灵感来自于操作系统中虚拟内存和分页的经典思想&#xff0c;它可以允许在非连续空间立存储连续的KV张量。具体来说&#xff0c;PagedAttention把每个序列的KV缓存进行了分块&#xff0c;每个块包含固定长度的token&#xff0c;而在计算attention时可…

自己造messagebox轮子

父组件中&#xff1a; import floatBox from ../messageBox data里加参数alertBox: 0, method中 changeAlertBox() {if (this.alertBox 0) {this.alertBox 1this.$refs.floatBox.changeAlertBox()} else {this.alertBox 0this.$refs.floatBox.changeAlertBox()}console.log…

MCU、MPU、SOC简介

文章目录 前言一、MCU二、MPU三、SOC总结 前言 随着处理器技术的不断发展&#xff0c;CPU(Central Processing Unit)的发展逐渐出现三种分支&#xff0c;分别是MCU(Micro Controller Unit&#xff0c;微控制器单元) 和MPU&#xff08;Micro Processor Unit&#xff0c;微处理器…

Angular系列教程之管道

文章目录 管道的基本概念使用内置管道创建自定义管道总结 在Angular中&#xff0c;管道&#xff08;Pipe&#xff09;是一个非常重要的概念。它们允许我们对数据进行转换、格式化和显示&#xff0c;并且可以轻松地在模板中使用。本篇文章将介绍Angular中的管道概念&#xff0c;…

正则表达式 (用于灵活匹配文本的表达式)

目录 . * . 用于匹配任意单个字符&#xff0c;除了换行符。 例如使用正则表达式 a.b, 它可以匹配aab、acb、a#b * 用于匹配前一个字符零次或多次。 例如&#xff0c;使用正则表达式 ab*c&#xff0c;它可以匹配 "ac"、"abc"、"abbc"&#…

SL3036国产新品 48V/60V电动车里程增程器供电芯片

随着电动车的普及&#xff0c;里程焦虑成为了很多电动车用户面临的问题。为了解决这个问题&#xff0c;SL3036国产新品应运而生&#xff0c;它是一款48V/60V电动车里程增程器供电芯片。这款芯片的出现&#xff0c;为电动车用户提供了更加可靠的续航里程&#xff0c;让他们在出行…

Linux 修改文件名称

创建文件 aaa [root@host144x180 qiao]# touch aaa [root@host144x180 qiao]# ll 总用量 4 -rw-r--r-- 1 root root 0 1月 16 13:57 aaa drwxr-xr-x 3 root root 4096 8月 12 17:14 update修改文件名称 cpmvrename复制 保留源文件:cp 源文件 目标文件[root@host144x18…