写个注解帮你净化使用分布式锁的重复操作
创始人
2024-01-25 21:16:50

Redisson+Aop实现分布式锁

前言

简介

Aop的意义

AOP 旨在从业务逻辑中分离出来通用逻辑,切面实现了跨越多种类型和对象的关注点(例如事务管理、日志记录、权限控制)的模块化。

例子

就以这段代码为例子,这段代码总是回去获取锁之后在执行完解开锁,基本上使用redisson作为分布式锁的代码都会以下几个操作

  1. 创建锁
  2. 获取锁
  3. 执行方法
  4. 解锁
   public Map> getCatalogJsonFromDbWithRedissonLock() {//1、占分布式锁。去redis占坑//(锁的粒度,越细越快:具体缓存的是某个数据,11号商品) product-11-lock//RLock catalogJsonLock = redissonClient.getLock("catalogJson-lock");//创建读锁RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("catalogJson-lock");RLock rLock = readWriteLock.readLock();Map> dataFromDb = null;try {rLock.lock();//加锁成功...执行业务dataFromDb = getDataFromDb();} finally {rLock.unlock();}return dataFromDb;}

思路

​ 重复的操作会让业务代码可读性变差,我们本着aop的思路 让业务代码专注于业务,来改造一下redisson锁获取值的方式优化,优化方式如下:

  1. 自定义注解 作用于方法上
  2. 用AOP来做redisson的获取锁和解锁还有存储redis的操作

代码实现

依赖

org.springframework.bootspring-boot-starter-data-redis

org.springframework.bootspring-boot-starter-web

org.apache.commonscommons-pool2

org.springframework.bootspring-boot-starter-aop

com.alibabafastjson1.2.79

org.springframework.bootspring-boot-devtoolsruntimetrue

org.redissonredisson-spring-boot-starter3.17.7

org.springframework.bootspring-boot-configuration-processortrue

org.projectlomboklomboktrue

配置

Application.properties

# 应用名称
spring.application.name=AspectRedisson
# 应用服务 WEB 访问端口
server.port=8080#redis配置
spring.redis.host=xxxxxx
spring.redis.password=admin

RedisConfig

配置序列化

/*** @package: com.hyc.aspectredisson.config.config* @className: RedisConfig* @author: 冷环渊 doomwatcher* @description: TODO* @date: 2022/11/15 19:21* @version: 1.0*/
@Configuration
public class RedisConfig {@Beanpublic RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate template = new RedisTemplate<>();//使用fastjson序列化FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);// value值的序列化采用fastJsonRedisSerializertemplate.setValueSerializer(fastJsonRedisSerializer);template.setHashValueSerializer(fastJsonRedisSerializer);// key的序列化采用StringRedisSerializertemplate.setKeySerializer(new StringRedisSerializer());template.setHashKeySerializer(new StringRedisSerializer());template.setConnectionFactory(redisConnectionFactory);return template;}
}

redissonConfig

/*** @projectName: AspectRedisson* @package: com.hyc.aspectredisson.config* @className: redissonConfig* @author: 冷环渊 doomwatcher* @description: TODO* @date: 2022/11/15 19:15* @version: 1.0*/
@Configuration
public class redissonConfig {@Beanpublic RedissonClient redissonClient() throws IOException {Config config = new Config();config.useSingleServer()//可以用"rediss://"来启用SSL连接.setAddress("redis://xxxxxx:6379").setPassword("admin");return Redisson.create(config);}
}

自定义注解

作用在方法上,有一个前缀参数用于区别缓存名称

/*** @projectName: AspectRedisson* @package: com.hyc.aspectredisson.config.annotation* @className: AddLock* @author: 冷环渊 doomwatcher* @description: TODO* @date: 2022/11/15 20:29* @version: 1.0*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface addSectionLock {/*** 缓存key的前缀* @return*/String prefix() default "cache";
}

切面类

​ 为方法上添加了注解的方法做环绕切面织入,实现有缓存就直接返回缓存数据,没有缓存就使用创建锁放入缓存中返回数据

/*** @author 冷环渊 Doomwatcher* @context: 处理切面环绕* @date: 2022/11/16 13:52*/
@Component
@Aspect
public class RedissonLockAspect {@Autowiredprivate RedisTemplate redisTemplate;@Autowiredprivate RedissonClient redissonClient;//  切addSectionLock注解@SneakyThrows@Around("@annotation(com.hyc.aspectredisson.annotation.addSectionLock)")public Object cacheAroundAdvice(ProceedingJoinPoint joinPoint){//  声明一个对象Object object = new Object();//  在环绕通知中处理业务逻辑 {实现分布式锁}//  获取到注解,注解使用在方法上!MethodSignature signature = (MethodSignature) joinPoint.getSignature();addSectionLock AddLock = signature.getMethod().getAnnotation(addSectionLock.class);//  获取到注解上的前缀String prefix = AddLock.prefix(); // test//  方法传入的参数Object[] args = joinPoint.getArgs();//  方法名String MethodName = ((MethodSignature) joinPoint.getSignature()).getMethod().getName();//  组成缓存的key 需要前缀+方法传入的参数String key = prefix+ MethodName+Arrays.asList(args).toString();//  防止redis ,redisson 出现问题!try {//  从缓存中获取数据//  类似于 redisTemplate.opsForValue().get(KEY);object = cacheHit(key,signature);//  判断缓存中的数据是否为空!if (object==null){//  从数据库中获取数据,并放入缓存,防止缓存击穿必须上锁//  key+":lock"String lockKey = prefix + "lock";//  准备上锁RLock lock = redissonClient.getLock(lockKey);boolean result = lock.tryLock(RedisConst.LOCK_EXPIRE_PX1, RedisConst.LOCK_EXPIRE_PX2, TimeUnit.SECONDS);//  上锁成功if (result){System.out.println("上锁成功"+result);try {//  表示执行方法体object = joinPoint.proceed(joinPoint.getArgs());//  判断object 是否为空if (object==null){//  防止缓存穿透Object object1 = new Object();redisTemplate.opsForValue().set(key, JSON.toJSONString(object1), RedisConst.KEY_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);//  返回数据return object1;}//  放入缓存redisTemplate.opsForValue().set(key, JSON.toJSONString(object),RedisConst.KEY_TIMEOUT,TimeUnit.SECONDS);//  返回数据return object;} finally {lock.unlock();}}else{//  上锁失败,睡眠自旋Thread.sleep(1000);return cacheAroundAdvice(joinPoint);//  理想状态//                  return object;}}return object;} catch (Throwable throwable) {throwable.printStackTrace();}//  如果出现问题数据库兜底return joinPoint.proceed(joinPoint.getArgs());}/***  表示从缓存中获取数据* @param key 缓存的key* @param signature 获取方法的返回值类型* @return*/private Object cacheHit(String key, MethodSignature signature) {//  通过key 来获取缓存的数据String strJson = (String) redisTemplate.opsForValue().get(key);//  表示从缓存中获取到了数据if (!StringUtils.isEmpty(strJson)){//  字符串存储的数据是什么?   就是方法的返回值类型Class returnType = signature.getReturnType();//  将字符串变为当前的返回值类型return JSON.parseObject(strJson,returnType);}return null;}
}

其余代码

常量类

public class RedisConst {public static final String KEY_PREFIX = "Test:";//单位:秒public static final long KEY_TIMEOUT = 60;public static final long KEY_TEMPORARY_TIMEOUT = 30;public static final long SKULOCK_EXPIRE_PX1 = 60;public static final long SKULOCK_EXPIRE_PX2 = 60;
}

测试

测试接口

@RestController
public class simpleController {@GetMapping(value = "/getInfo")@addSectionLock(prefix = RedisConst.KEY_PREFIX)public String getinfo(String name){userEntity userEntity = new userEntity();userEntity.setName(name);userEntity.setUserWare(100);return JSON.toJSONString(userEntity);}}

测试

​ 我们之前切面类中有设置获取锁成功的输出日志,按照实现逻辑,只需要执行一次存入缓存的时候需要获取锁,所以在这个日志指挥打印一次,简单测试那就开两个浏览器页面反复获取数据

image-20221116205847384

​ 在多次点击之后,还是只输出一次,这个模式就算是实现成功了,之后也有用apifox去测试,结果是没问题的,只要有缓存就不会去刷新方法,业务代码只需要加上注解设置前缀就可以完成加入缓存与存在直接调用。就算多次刷新也不会对数据库造成压力。

相关内容

热门资讯

北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
插入行快捷键 excel添加空... 如下图所示,像这样在两行之间插入多行空白行的操作,你会几种?可能很多人都会觉得,不就是插入空行嘛,简...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
疑难件什么意思 淘宝退货为什么... 01 常见售后类型对于现在主流电商来说,通常支持的售后形式包括仅退款,退货退款,换货,补寄。用户根据...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...