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.hqwc.cn/news/409839.html

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

相关文章

Python爬虫实战:IP代理池助你突破限制,高效采集数据

当今互联网环境中&#xff0c;为了应对反爬虫、匿名访问或绕过某些地域限制等需求&#xff0c;IP代理池成为了一种常用的解决方案。IP代理池是一个包含多个可用代理IP地址的集合&#xff0c;可以通过该代理池随机选择可用IP地址来进行网络请求。 IP代理池是一组可用的代理IP地址…

纯c++简易的迷宫小游戏

一个用c写的黑框框迷宫 适合新手入门学习 也适合大学生小作业 下面附上代码 总体思路 初始化游戏界面&#xff1a;设置迷宫的大小&#xff08;WIDTH和HEIGH&#xff09;&#xff0c;生成迷宫地图&#xff08;map&#xff09;&#xff0c;包括墙壁、空地、起点和终点。显示…

Spark与HBase的集成与数据访问

Apache Spark和Apache HBase分别是大数据处理和分布式NoSQL数据库领域的两个重要工具。在本文中&#xff0c;将深入探讨如何在Spark中集成HBase&#xff0c;并演示如何通过Spark访问和操作HBase中的数据。将提供丰富的示例代码&#xff0c;以便更好地理解这一集成过程。 Spark…

物联网介绍

阅读引言&#xff1a; 本文从多方面叙述物联网的定义以及在物联网当中的各种通信的介绍。 一、物联网的定义 1.1 通用的定义 物联网&#xff08;Internet of Things&#xff0c;IOT&#xff1b;也称为Web of Things&#xff09;是指通过各种信息传感设 备&#xff0c;如传感器、…

STM32 定时器输入捕获2——捕获高电平时长

由上图我们可以知道&#xff0c;高电平时间t2-t1。在代码中&#xff0c;可以记录此时t1的时间然后再记录t2的时间&#xff0c;t2-t1&#xff0c;就是我们所想要的答案。 但是&#xff0c;还有更简单一点点的&#xff0c;当到达t1的时候&#xff0c;我们把定时器清零&#xff0c…

力扣电话号码的组合

文章目录 题目说明做题思路代码实现代码解析 题目链接 题目说明 首先我们先分析一下这个题目题目中说呢先给出一个字符串这个字符串其实就是这个九键数字我们要按照要求将数字所代表的字符进行自由组合形成一个字符串并且这个字符串的长度和输入的数字字符串长度相同&#xff0…

Ansible Filter滤波器的使用(二)

一、【说在前面】 Ansible Filter一般被称为滤波器或者叫过滤器。 这个东西初次听到以为是什么科学计算的东西&#xff0c;但是想来ansible不太可能有什么滤波操作&#xff0c;所以这个东西本质是一个数值筛选器&#xff0c;内置函数&#xff0c;本质是一个为了做区别化的工具…

【MapReduce】对员工数据按照部门分区并对每个分区排序

员工信息全部存储在emp.csv文件中&#xff0c;员工的属性有&#xff1a;员工id、名称、职位、领导id、雇佣时间、工资、奖金、部门号。 在MapReduce中想要使用员工的信息&#xff0c;需要对员工进行序列化处理。因为MapReduce是一个分布式框架数据会在不同节点之间进行传输&…

ZooKeeper 实战(五) Curator实现分布式锁

文章目录 ZooKeeper 实战(五) Curator实现分布式锁1.简介1.1.分布式锁概念1.2.Curator 分布式锁的实现方式1.3.分布式锁接口 2.准备工作3.分布式可重入锁3.1.锁对象3.2.非重入式抢占锁测试代码输出日志 3.3.重入式抢占锁测试代码输出日志 4.分布式非可重入锁4.1.锁对象4.2.重入…

R语言【paleobioDB】——pbdb_orig_ext():绘制随着时间变化而出现的新类群

Package paleobioDB version 0.7.0 paleobioDB 包在2020年已经停止更新&#xff0c;该包依赖PBDB v1 API。 可以选择在Index of /src/contrib/Archive/paleobioDB (r-project.org)下载安装包后&#xff0c;执行本地安装。 Usage pbdb_orig_ext (data, rank, temporal_extent…

Spark---累加器和广播变量

文章目录 1.累加器实现原理2.自定义累加器3.广播变量 1.累加器实现原理 累加器用来把 Executor 端变量信息聚合到 Driver 端。在 Driver 程序中定义的变量&#xff0c;在Executor 端的每个 Task 都会得到这个变量的一份新的副本&#xff0c;每个 task 更新这些副本的值后&…

【windows】右键添加git bash here菜单

在vs 里安装了git for windows 后&#xff0c;之前git-bash 右键菜单消失了。难道是git for windows 覆盖了原来自己安装的git &#xff1f;大神给出解决方案 手动添加Git Bash Here到右键菜单&#xff08;超详细&#xff09; 安装路径&#xff1a;我老的 &#xff1f; vs的gi…