本文基于 Spring Boot 2.7.x + MyBatis Plus 3.5.9,演示如何通过 Lock4j 与 Redisson 实现高可靠的分布式锁方案,解决高并发场景下的资源竞争问题。
一、依赖配置关键点
1.1 Maven 依赖(pom.xml)
xml
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.41.0</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</exclusion>
<exclusion>
<groupId>org.redisson</groupId>
<!-- 使用 redisson-spring-data-27 替代,解决 Tuple NoClassDefFoundError 报错 -->
<artifactId>redisson-spring-data-34</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-data-27</artifactId>
<version>3.41.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
<version>2.2.7</version>
<exclusions>
<exclusion>
<artifactId>redisson-spring-boot-starter</artifactId>
<groupId>org.redisson</groupId>
</exclusion>
</exclusions>
</dependency>
1.2 依赖关系说明
依赖 | 作用 | 版本控制策略 |
---|---|---|
lock4j-redisson-* |
Lock4j 分布式锁核心实现 | 继承自项目 BOM 统一管理 |
redisson-spring-boot-starter |
Redisson 客户端 Starter | 显式指定版本保证兼容性 |
redisson-spring-data-27 |
Spring Data 2.7 适配模块 | 与 Spring Boot 2.7.x 版本严格对应 |
二、为什么需要显式排除依赖?
2.1 解决版本冲突问题
- Lock4j 默认依赖的 Redisson 版本可能较低 ,通过
<exclusions>
排除后使用项目统一管理的 Redisson 3.41.0 - Spring Data 版本适配 :Spring Boot 2.7.x 对应 Spring Data 2.7,需使用
redisson-spring-data-27
模块
2.2 避免不必要依赖
- 排除
spring-boot-starter-actuator
:若项目未使用监控端点,减少无用依赖 - 排除
redisson-spring-data-34
:防止与 Spring Data 2.7 产生兼容性问题
三、配置验证(application.yml)
yaml
spring:
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
redis:
host: 127.0.0.1 # 地址
port: 6379 # 端口
database: 0 # 数据库索引
password: 123456 # 密码,建议生产环境开启
# Lock4j 配置项
lock4j:
acquire-timeout: 3000 # 获取锁等待超时时间,默认为 3000 毫秒
expire: 30000 # # 默认锁过期时间(毫秒),默认为 30 毫秒
store-type: redis # 使用 Redis 实现
四、锁失败策略配置(核心防御机制)
4.1 DefaultLockFailureStrategy 源码解析
java
/**
* 自定义获取锁失败策略,抛出 {@link ServiceException} 异常
*/
@Slf4j
public class DefaultLockFailureStrategy implements LockFailureStrategy {
@Override
public void onLockFailure(String key, Method method, Object[] arguments) {
log.debug("[onLockFailure][线程:{} 获取锁失败,key:{} 获取失败:{} ]", Thread.currentThread().getName(), key, arguments);
throw new ServiceException(GlobalErrorCodeConstants.LOCKED);
}
}
关键设计点:
- 日志分级 :使用
debug
级别避免生产环境日志膨胀 - 异常标准化 :通过
GlobalErrorCodeConstants.LOCKED
统一返回 "请求过于频繁" 提示 - 上下文保留:记录线程、锁Key、方法参数等关键信息便于问题排查
五、自动装配机制(集成关键)
5.1 DyhLock4jConfiguration 配置类
java
@AutoConfiguration(before = LockAutoConfiguration.class)
@ConditionalOnClass(name = "com.baomidou.lock.annotation.Lock4j")
public class DyhLock4jConfiguration {
@Bean
public DefaultLockFailureStrategy lockFailureStrategy() {
return new DefaultLockFailureStrategy();
}
}
注解解析:
注解 | 作用 |
---|---|
@AutoConfiguration |
Spring Boot 3.0+ 新特性,替代传统 @Configuration 更精准控制加载顺序 |
before = LockAutoConfiguration.class |
确保在 Lock4j 默认配置前加载,避免 Bean 冲突 |
@ConditionalOnClass |
存在 Lock4j 注解时才生效,实现条件化装配 |
5.2 配置类加载流程说明
流程分解
text
1. Spring Boot 启动
│
└─▶ 扫描所有 AutoConfiguration 类
│
├─▶ 检测是否存在 Lock4j 注解(@ConditionalOnClass)
│ │
│ ├─ 存在 → 加载 DyhLock4jConfiguration
│ │ │
│ │ └─▶ 注册 DefaultLockFailureStrategy Bean
│ │ │
│ │ └─▶ 初始化 Lock4j 组件
│ │
│ └─ 不存在 → 跳过分布式锁配置
│
└─▶ 继续其他自动配置
关键节点说明
步骤 | 说明 | 对应代码片段 |
---|---|---|
条件检测 | @ConditionalOnClass 注解检查 classpath 是否存在 com.baomidou.lock.annotation.Lock4j 类 | @ConditionalOnClass(name = "com.baomidou.lock.annotation.Lock4j") |
配置顺序控制 | @AutoConfiguration(before = LockAutoConfiguration.class) 确保自定义配置在 Lock4j 默认配置前加载 | @AutoConfiguration(before = LockAutoConfiguration.class) |
Bean 注册 | 通过 @Bean 注解将失败策略注入 Spring 容器 | @Bean public DefaultLockFailureStrategy lockFailureStrategy() |
六、实战开发示例
java
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderMapper orderMapper;
// 分布式锁注解
@Lock4j(
name = "order_create_#{#orderDTO.userId}", // 动态锁名(SpEL)
expire = 10000, // 锁自动释放时间(毫秒)
acquireTimeout = 2000 // 获取锁等待时间
)
@Transactional
public void createOrder(OrderDTO orderDTO) {
// 1. 幂等校验
Order existOrder = orderMapper.selectByUserId(orderDTO.getUserId());
if (existOrder != null) {
throw new ServiceException("订单已存在");
}
// 2. 业务操作(如扣减库存)
reduceStock(orderDTO.getProductId());
// 3. 创建订单
Order order = new Order().setUserId(orderDTO.getUserId());
orderMapper.insert(order);
}
}