分布式锁切面(DistributeLockAspect)代码解读

分布式锁切面(DistributeLockAspect)代码解读

  • 这是一个基于Spring AOP和Redisson实现的分布式锁切面,用于在方法执行前获取分布式锁,执行后释放锁。下面我将从多个维度详细解析这段代码:

一、核心功能概述

该切面主要实现以下功能:

  • 通过@DistributeLock注解标记需要加锁的方法

  • 支持动态生成锁的key(使用SpEL表达式)

  • 提供多种加锁策略(立即加锁、尝试加锁、带过期时间的锁)

  • 确保锁的最终释放(在finally块中)

二、代码结构解析

  1. 类定义与依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Aspect
    @Component
    @Order(Integer.MIN_VALUE) // 确保最先执行
    public class DistributeLockAspect {
    private RedissonClient redissonClient; // Redisson客户端

    // 构造函数注入
    public DistributeLockAspect(RedissonClient redissonClient) {
    this.redissonClient = redissonClient;
    }
    }
    @Aspect:声明这是一个切面类

    @Order(Integer.MIN_VALUE):确保这个切面在调用链中最早执行

    通过构造函数注入RedissonClient,用于操作分布式锁
  2. 核心切面方法

    1
    2
    3
    4
    5
    @Around("@annotation(cn.hollis.nft.turbo.lock.DistributeLock)")
    public Object process(ProceedingJoinPoint pjp) throws Exception
    使用@Around注解拦截所有带有@DistributeLock注解的方法

    ProceedingJoinPoint参数可以获取被拦截方法的信息

三、关键实现逻辑

  1. 锁Key的生成
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    String key = distributeLock.key();
    if (DistributeLockConstant.NONE_KEY.equals(key)) {
    // 使用SpEL表达式动态生成key
    SpelExpressionParser parser = new SpelExpressionParser();
    Expression expression = parser.parseExpression(distributeLock.keyExpression());
    // 设置上下文变量
    EvaluationContext context = new StandardEvaluationContext();
    Object[] args = pjp.getArgs();
    String[] parameterNames = discoverer.getParameterNames(method);
    // 绑定参数到上下文
    for (int i = 0; i < parameterNames.length; i++) {
    context.setVariable(parameterNames[i], args[i]);
    }
    key = String.valueOf(expression.getValue(context));
    }
    String scene = distributeLock.scene();
    String lockKey = scene + "#" + key; // 最终锁key

支持静态key和动态key两种方式

动态key使用SpEL表达式,可以从方法参数中取值

最终key格式为scene#key,便于分类管理

  1. 加锁逻辑
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    RLock rLock= redissonClient.getLock(lockKey);
    try {
    boolean lockResult = false;
    if (waitTime == DistributeLockConstant.DEFAULT_WAIT_TIME) {
    // 立即加锁逻辑
    if (expireTime == DistributeLockConstant.DEFAULT_EXPIRE_TIME) {
    rLock.lock(); // 永久锁
    } else {
    rLock.lock(expireTime, TimeUnit.MILLISECONDS); // 带过期时间的锁
    }
    lockResult = true;
    } else {
    // 尝试加锁逻辑
    if (expireTime == DistributeLockConstant.DEFAULT_EXPIRE_TIME) {
    lockResult = rLock.tryLock(waitTime, TimeUnit.MILLISECONDS);
    } else {
    lockResult = rLock.tryLock(waitTime, expireTime, TimeUnit.MILLISECONDS);
    }
    }

    if (!lockResult) {
    throw new DistributeLockException("acquire lock failed... key : " + lockKey);
    }

    // 执行目标方法
    response = pjp.proceed();
    } finally {
    // 释放锁
    if (rLock.isHeldByCurrentThread()) {
    rLock.unlock();
    }
    }

支持四种加锁模式:

  • 永久锁(无过期时间)

  • 带过期时间的锁

  • 尝试获取锁(带等待时间)

  • 尝试获取带过期时间的锁(带等待时间和过期时间)

确保锁最终会被释放(finally块)

四、设计亮点

灵活的锁Key生成:支持静态key和SpEL动态key

多种加锁策略:满足不同业务场景需求

线程安全:确保只有持有锁的线程才能释放锁

日志完备:记录加锁、释放锁的关键信息

异常处理:加锁失败抛出特定异常

五、使用示例

1
2
3
4
5
6
7
8
9
10
11
@Service
public class OrderService {
@DistributeLock(
scene = "order",
keyExpression = "#orderId", // 使用SpEL从参数获取值
expireTime = 5000
)
public void processOrder(String orderId) {
// 业务逻辑
}
}

六、改进建议

锁的可重入性:Redisson的RLock本身支持可重入,但可以增加重入计数日志

锁的自动续期:对于长时间任务,可以实现锁的自动续期

更细粒度的异常处理:区分业务异常和锁相关的异常

性能监控:添加锁获取时间、持有时间等监控指标

这个切面实现了一个完整且健壮的分布式锁解决方案,可以很好地应用于需要分布式锁的业务场景中。

 wechat
天生我才必有用