lock4j 库中的 @Lock4j 注解进行全面的概述和深度的源码级剖析。
首先需要明确:@Lock44j
并非 Spring 官方提供的注解,它是国内开源社区(主要来自 baomidou
团队)提供的一个分布式锁注解框架,通常与 lock4j
库一起使用。它的设计理念是让开发者通过一个简单的注解,就能优雅地实现分布式锁逻辑,其底层可以适配不同的锁实现(如 Redis, Redisson, Zookeeper 等)。
一、@Lock4j
详细概述
1. 核心功能
@Lock4j
的核心功能是声明式分布式锁。它通过 AOP 在方法执行前尝试获取锁,执行后自动释放锁,将分布式锁的通用逻辑与业务代码完全解耦。
2. 核心注解属性
@Lock4j
注解通常包含以下属性,用于精细控制锁的行为:
属性 | 类型 | 默认值 | 描述 |
---|---|---|---|
name | String | "" | 锁的名称。与 key 共同组成最终的锁标识符。 |
key | String | "" | 锁的键。支持 Spring EL (SpEL) 表达式,用于从方法参数中动态获取值,实现更细粒度的锁(如 #user.id )。name 和 key 用冒号连接形成最终锁键。 |
keys | String[] | {} | 锁的键数组。同样支持 SpEL,多个键会被拼接起来形成最终锁键。 |
expire | long | 30000 | 锁的自动释放时间(毫秒)。防止因服务宕机导致死锁。 |
acquireTimeout | long | 10000 | 获取锁的最大等待时间(毫秒)。超过这个时间还未获取到锁则抛出异常。 |
executor | Class<? extends LockExecutor> | LockExecutor.class | 锁执行器。用于指定获取和释放锁的具体实现(如 Redis、Zookeeper)。 |
onLockFailure | LockFailureStrategy | ThrowException | 获取锁失败时的处理策略。例如:快速失败抛出异常、忽略并继续执行、重试等。 |
3. 工作流程
在使用时v,引入starter依赖,源码如下所示:
<dependency><groupId>com.baomidou</groupId><artifactId>lock4j-redis-template-spring-boot-starter</artifactId><version>2.2.7</version></dependency>
@Lock4j`注解的工作流程如下图所示:
—
二、实现源码剖析
我们以 lock4j
库的典型实现为例进行剖析。其核心架构分为三层:注解层、切面层、执行器层。
1. 注解定义 (org.springblade.core.lock.annotation.Lock4j
)
这是注解的声明,非常简单。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lock4j {String name() default "";String key() default "";String[] keys() default {};long expire() default 30000;long acquireTimeout() default 10000;Class<? extends LockExecutor> executor() default LockExecutor.class;LockFailureStrategy onLockFailure() default LockFailureStrategy.THROW_EXCEPTION;
}
// 失败策略枚举
public enum LockFailureStrategy {THROW_EXCEPTION, // 抛出异常IGNORE, // 忽略,继续执行方法CONTINUE // 忽略,直接返回(不执行方法)
}
2. 切面层 (LockAspect
) - 核心中的核心
这是整个功能的大脑,负责协调整个流程。
@Aspect
@AllArgsConstructor // Lombok,注入依赖
public class LockAspect {// 锁模板,提供了加锁/解锁的通用高级APIprivate final LockTemplate lockTemplate;// SpEL表达式解析器private final ExpressionParser expressionParser = new SpelExpressionParser();// 参数名发现器,用于解析SpEL中的#p0, #a0, #user等参数名private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();@Around(value = "@annotation(lock4j)")public Object around(ProceedingJoinPoint point, Lock4j lock4j) throws Throwable {// 1. 动态解析SpEL,生成最终的锁KeyString lockKey = getLockKey(point, lock4j);// 2. 根据注解配置,获取对应的锁执行器(如RedisExecutor)LockExecutor lockExecutor = LockExecutorFactory.getExecutor(lock4j.executor());// 3. 构建锁信息对象(封装了key, expire, acquireTimeout等)LockInfo lockInfo = LockInfo.builder().lockKey(lockKey).leaseTime(lock4j.expire()).acquireTimeout(lock4j.acquireTimeout()).build();// 4. 尝试获取锁boolean acquired = lockTemplate.acquire(lockInfo, lockExecutor, lock4j.acquireTimeout());// 5. 根据获取结果和失败策略进行处理if (!acquired) {return handleLockFailure(point, lock4j.onLockFailure(), lockInfo);}// 6. 获取锁成功,执行原方法,并在finally块中释放锁try {return point.proceed();} finally {lockTemplate.release(lockInfo, lockExecutor);}}/*** 核心方法:解析SpEL,拼接最终LockKey*/private String getLockKey(ProceedingJoinPoint point, Lock4j lock4j) {// 获取方法签名和参数MethodSignature signature = (MethodSignature) point.getSignature();Object[] args = point.getArgs();Method method = signature.getMethod();// 创建SpEL上下文EvaluationContext context = new MethodBasedEvaluationContext(null, method, args, parameterNameDiscoverer);// 解析 name 部分String namePart = lock4j.name();// 解析 keys 部分(支持多个key)List<String> keyParts = new ArrayList<>();if (ArrayUtil.isNotEmpty(lock4j.keys())) {for (String keyExpr : lock4j.keys()) {if (StringUtil.isNotBlank(keyExpr)) {String value = expressionParser.parseExpression(keyExpr).getValue(context, String.class);keyParts.add(value);}}}// 解析单个 key (兼容旧版)else if (StringUtil.isNotBlank(lock4j.key())) {String value = expressionParser.parseExpression(lock4j.key()).getValue(context, String.class);keyParts.add(value);}// 拼接最终的LockKey: name:keyPart1:keyPart2:...return Stream.of(Collections.singletonList(namePart), keyParts).flatMap(Collection::stream).filter(StringUtil::isNotBlank).collect(Collectors.joining(":"));}// 处理获取锁失败的逻辑private Object handleLockFailure(/*...*/) { /*...*/ }
}
3. 执行器层 (LockExecutor
) - 底层实现
这是一个抽象接口,定义了锁操作的基本契约,不同的实现类对接不同的中间件。
public interface LockExecutor {// 尝试获取锁boolean acquire(LockInfo lockInfo);// 释放锁void release(LockInfo lockInfo);
}// Redis 实现 (基于 Lua 脚本保证原子性)
public class RedisLockExecutor implements LockExecutor {private final StringRedisTemplate stringRedisTemplate;// 加锁 Lua 脚本: SET lock_key unique_value NX PX expire_timeprivate static final String LOCK_SCRIPT = "if redis.call('set', KEYS[1], ARGV[1], 'NX', 'PX', ARGV[2]) then return 1 else return 0 end";// 解锁 Lua 脚本: 只有锁的值匹配才删除,防止误删private static final String UNLOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";@Overridepublic boolean acquire(LockInfo lockInfo) {// 生成一个唯一值(UUID),用于标识加锁的客户端,防止误删String lockValue = UUID.randomUUID().toString();lockInfo.setLockValue(lockValue);Long result = stringRedisTemplate.execute(new DefaultRedisScript<>(LOCK_SCRIPT, Long.class),Collections.singletonList(lockInfo.getLockKey()),lockValue,String.valueOf(lockInfo.getLeaseTime()));return result != null && result == 1;}@Overridepublic void release(LockInfo lockInfo) {stringRedisTemplate.execute(new DefaultRedisScript<>(UNLOCK_SCRIPT, Long.class),Collections.singletonList(lockInfo.getLockKey()),lockInfo.getLockValue() // 传入唯一值进行校验);}
}// Redisson 实现 (更简单,利用其原生API)
public class RedissonLockExecutor implements LockExecutor {private final RedissonClient redissonClient;@Overridepublic boolean acquire(LockInfo lockInfo) {RLock lock = redissonClient.getLock(lockInfo.getLockKey());try {return lock.tryLock(lockInfo.getAcquireTimeout(), lockInfo.getLeaseTime(), TimeUnit.MILLISECONDS);} catch (InterruptedException e) {Thread.currentThread().interrupt();return false;}}@Overridepublic void release(LockInfo lockInfo) {RLock lock = redissonClient.getLock(lockInfo.getLockKey());if (lock.isHeldByCurrentThread()) {lock.unlock();}}
}
4. 模板层 (LockTemplate
) - 工具层
提供一些模板方法,对执行器的调用进行封装,可能加入一些重试逻辑或日志。
public class LockTemplate {public boolean acquire(LockInfo lockInfo, LockExecutor executor, long acquireTimeout) {// 可能会在此处实现重试机制long endTime = System.currentTimeMillis() + acquireTimeout;while (System.currentTimeMillis() < endTime) {if (executor.acquire(lockInfo)) {return true;}try {Thread.sleep(100); // 短暂休眠后重试} catch (InterruptedException e) {Thread.currentThread().interrupt();break;}}return false;}public void release(LockInfo lockInfo, LockExecutor executor) {executor.release(lockInfo);}
}
三、总结与最佳实践
总结:
@Lock4j
是一个基于 Spring AOP 和 SpEL 的强大声明式分布式锁框架。它通过注解驱动、执行器插件化和Key动态化的设计,将复杂的分布式锁技术细节完全隐藏,让开发者能够专注于业务逻辑。
最佳实践:
- 锁粒度:使用
key
或keys
属性设置细粒度锁,避免使用粗粒度的全局锁,提高并发性能。例如:@Lock4j(name = "order", key = "#id")
。 - 超时设置:
expire
时间应大于业务方法的平均执行时间,但不宜过长。acquireTimeout
根据业务容忍度设置。 - 失败策略:根据业务场景选择合适的
onLockFailure
策略。支付等关键业务应THROW_EXCEPTION
,一些统计更新场景可以IGNORE
。 - 监控:对加锁失败(
LockFailureException
)进行监控和告警,及时发现系统瓶颈或问题。 - 选择执行器:生产环境推荐使用
RedissonLockExecutor
,因为它提供了看门狗自动续期、可重入等高级特性,更为可靠。