1、核心代码
我们日常是如何利用Redisson实现分布式锁的呢?
是否大多数情况都遵循了某种固定的“模式”或“模板”?既然这是一个常见的模式,那么我们为何不将其抽象出来,以便更加便捷和高效地使用呢?
// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
try {
...业务代码
} finally {
lock.unlock();
}
}
2、抽出分布式锁工具类
提炼出一个LockService方法,将分布式锁的通用模板封装于其中。这样一来,在需要加锁的地方,我们仅需指定锁的key,并通过supplier函数传入需在锁内执行的代码块。这种设计不仅显著提升了代码的复用性,还使得加锁逻辑更加清晰、易于管理。如此,我们可以更加高效、便捷地处理分布式锁的需求,从而优化整个系统的并发控制。
@Service
@Slf4j
public class LockService {
@Autowired
private RedissonClient redissonClient;
public <T> T executeWithLock(String key, int waitTime, TimeUnit unit, SupplierThrow<T> supplier) throws Throwable {
RLock lock = redissonClient.getLock(key);
boolean lockSuccess = lock.tryLock(waitTime, unit);
if (!lockSuccess) {
//抛出自定义异常
throw new Exception();
}
try {
return supplier.get();//执行锁内的代码逻辑
} finally {
lock.unlock();
}
}
}
使用起来就方便了
lockService.executeWithLock(key, 10, TimeUnit.SECONDS, ()->{
//执行业务逻辑
。。。。。
return null;
});
如果我们不需要排队等锁,甚至还能重载方法减少两个参数。
lockService.executeWithLock(key, ()->{
//执行业务逻辑
。。。。。
return null;
});
3、注解实现分布式锁
锁工具类已全面涵盖了分布式锁的核心功能实现。我们引入注解的初衷,正是为了简化这一核心功能的使用方式,让开发者能够更轻松地运用分布式锁。如同诸多底层的SDK,它们常通过接口调用的方式来实现核心功能,而注解的加入则进一步增强了这些功能的便捷性。
现在,让我们深入探讨一个常见的场景:在分布式系统中,我们常需在controller层或service层的特定方法上应用分布式锁。多数情况下,加锁所需的key并非固定不变,而是需要根据方法的输入参数进行动态组合。那么,我们是否有可能利用EL表达式来实现key的动态组合呢?
答案无疑是肯定的。通过EL表达式,我们能够灵活地根据方法的输入参数来动态生成锁的key。这不仅增强了锁的灵活性,还使得锁的使用更加贴近实际需求。因此,结合注解与EL表达式,我们可以构建一个既强大又易用的分布式锁解决方案,为分布式系统的并发控制提供坚实支撑。
1.创建注解@RedissonLock
/**
* 分布式锁注解
*/
@Retention(RetentionPolicy.RUNTIME)//运行时生效
@Target(ElementType.METHOD)//作用在方法上
public @interface RedissonLock {
/**
* key的前缀,默认取方法全限定名,除非我们在不同方法上对同一个资源做分布式锁,就自己指定
*
* @return key的前缀
*/
String prefixKey() default "";
/**
* springEl 表达式
*
* @return 表达式
*/
String key();
/**
* 等待锁的时间,默认-1,不等待直接失败,redisson默认也是-1
*
* @return 单位秒
*/
int waitTime() default -1;
/**
* 等待锁的时间单位,默认毫秒
*
* @return 单位
*/
TimeUnit unit() default TimeUnit.MILLISECONDS;
}
约定大于配置的思想,我们的大多数参数都是可以默认的。
很多时候我们的锁都是针对方法的,要锁同一处地方,调用同一个方法就好了,这样前缀可以直接默认根据类+方法名
来实现,同样针对特例我们也提供了自己指定前缀的入口。
2.实现切面RedissonLockAspect
@Slf4j
@Aspect
@Component
@Order(0)//确保比事务注解先执行,分布式锁在事务外
public class RedissonLockAspect {
@Autowired
private LockService lockService;
@Around("@annotation(common.annotation.RedissonLock)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
RedissonLock redissonLock = method.getAnnotation(RedissonLock.class);
String prefix = StrUtil.isBlank(redissonLock.prefixKey()) ? SpElUtils.getMethodKey(method) : redissonLock.prefixKey();//默认方法限定名+注解排名(可能多个)
String key = SpElUtils.parseSpEl(method, joinPoint.getArgs(), redissonLock.key());
return lockService.executeWithLock(prefix + ":" + key, redissonLock.waitTime(), redissonLock.unit(), joinPoint::proceed);
}
}
切面其实很简单,构建key=前缀+el表达式,然后把参数都传进去,调用我们核心功能的工具类LockService
。
SpElUtils
代码请查看https://blog.csdn.net/qq_40694890/article/details/137011707
3.使用
@RedissonLock(key = "#uid")
public void setMsgMark(Long uid){
//业务代码
}
使用起来就非常方便了,对uid加锁,只要一个注解搞定。如果需要等待,再加个等待时间就行。