背景介绍
分布式锁作为分布式架构体系中重要的一项技术点,在分布式的系统中有广泛的应用。分布式锁最流行的实现方式有两种:
- 基于Redis实现
- 基于ZK实现
基于Redis的实现有很多开源的项目,最出名的就是 redisson 。其API使用的一个示例如下:
RLock lock = redissonClient.getLock(key);
try {
if (lock.tryLock(60, TimeUnit.SECONDS)) {
......do samething
} else {
......do samething
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock.isLocked()) {
lock.unlock();
}
}
具体其他的API使用方式不做具体讨论,此处可以看到,使用起来虽然比较简单,但是有很多的重复代码。这样的代码写到业务代码逻辑中就非常糟糕了。
基于这样一个情况,想到利用自定义注解和AOP将redisson的API封装起来,方便后续使用注解就可以用分布式锁。
代码实现
- 注解定义:
/**
* @author
* <p>
* 为方便使用基于开源项目Redisson的Redis分布式锁
* 本注解基于Redisson的API封装
* waitTime 等待时间 如果注解中不赋值,则使用默认值
* leaseTime 锁持有时间,如果注解中不赋值,则调用无leaseTime参数的API
* lockKey 用于指定被注解方法参数中做为锁的KEY的参数名称。如果不指定,则使用类名+方法名做为KEY
* lockKeyPrefix 用于指定KEY的前缀。
* @Date 2021-12
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLockAnnotation {
long waitTime() default 60;
long leaseTime() default -1;
String lockKey() default "";
String lockKeyPrefix() default "";
}
- 切面定义
@Aspect
@Component
public class RedisLock {
private static final Logger logger = LoggerFactory.getLogger(RedisLock.class);
@Autowired
private RedissonClient redissonClient;
@Pointcut("@annotation(cn.ysheng.common.aspect.RedisLockAnnotation)")
private void anyMethod() {
}
@Around("anyMethod()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object result;
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
RedisLockAnnotation redisLockAnnotation = method.getAnnotation(RedisLockAnnotation.class);
if (redisLockAnnotation == null) {
Method realMethod = pjp.getTarget().getClass().getDeclaredMethod(method.getName(), method.getParameterTypes());
redisLockAnnotation = realMethod.getAnnotation(RedisLockAnnotation.class);
}
String key;
String lockKey = redisLockAnnotation.lockKey();
if (ObjectUtil.isEmpty(lockKey)) {
//如果未设置key字段则使用方法名做为KEY
key = method.getDeclaringClass().getName() + method.getName();
} else {
//访问目标方法的参数,获取参数值作为KEY基础
Object[] args = pjp.getArgs();
if (args != null && args.length > 0) {
key = getFieldValue(lockKey, args);
if (ObjectUtil.isEmpty(key)) {
throw new Exception("参数中不存在key字段属性:" + lockKey);
}
} else {
logger.error("目标方法参数为空!");
throw new Exception("目标方法参数为空!");
}
}
//锁的key拼接前缀
String lockKeyPrefix = redisLockAnnotation.lockKeyPrefix();
if (ObjectUtil.isNotEmpty(lockKeyPrefix)) {
key = lockKeyPrefix + key;
}
//加锁
long waitTime = redisLockAnnotation.waitTime();
long leaseTime = redisLockAnnotation.leaseTime();
RLock lock = redissonClient.getLock(key);
try {
if (-1 == leaseTime) {
if (lock.tryLock(waitTime, TimeUnit.SECONDS)) {
//执行
result = pjp.proceed();
} else {
logger.info(key + "_未获取到锁");
throw new BusinessGetLockFailureException(key + "_未获取到锁");
}
} else {
if (lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS)) {
//执行
result = pjp.proceed();
} else {
logger.info(key + "_未获取到锁");
throw new BusinessGetLockFailureException(key + "_未获取到锁");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
throw new BusinessLockInterruptedException(key + "_锁中断异常");
} finally {
if (lock.isLocked()) {
lock.unlock();
}
}
return result;
}
public String getFieldValue(String fieldName, Object[] args) {
if (args == null || ObjectUtil.isEmpty(fieldName)) {
return null;
}
Field field;
try {
for (Object object : args) {
if (isInstance(object)) {
System.out.println(object);
return String.valueOf(object);
} else {
field = object.getClass().getDeclaredField(fieldName);
//设置对象的访问权限,保证对private的属性的访问
field.setAccessible(true);
return String.valueOf(field.get(object));
}
}
return null;
} catch (Exception e) {
return null;
}
}
private boolean isInstance(Object object) {
if (String.class.isInstance(object)) {
return true;
} else if (Long.class.isInstance(object)) {
return true;
} else if (Integer.class.isInstance(object)) {
return true;
} else {
return false;
}
}
}
使用实例:
@RedisLockAnnotation
public ResponseEntity<Result<String>> test(String parm) {
......do samething
}
@RedisLockAnnotation(waitTime = 5,leaseTime = 60,lockKey = "trace",lockKeyPrefix = "keyprefix")
public ResponseEntity<Result<String>> test(@RequestBody TestRequestDto testRequestDto) {
......do samething
}