
“重点是大模型的应用,知识不等于能力”,然后就用大模型写一个分布式锁的组件,写的又快又好,分享一下。


下面来分享下从零到发布:Spring声明式分布式锁的设计、实现与Central发布全记录。
一个注解,兼顾 Spring Boot 2.x 与 Spring Boot 3.X;一套代码,从设计到 Central 发布。附完整可复用的自旋策略、安全释放与发布 SOP。
在高并发、分布式业务中,「锁」是一个无法绕开的主题。但你是否也曾纠结于:
针对这些问题,设计并实现了一个 Spring 注解式分布式锁组件,它不仅解决了上述痛点,还成功发布到了 Maven Central。本文将与你分享从设计、实现、测试到发布的完整过程,包含可直接复用的代码示例和详细的发布避坑指南。
1. 声明式开发体验
在业务中,我们频繁遇到“幂等性处理”或“并发下保护关键资源”的需求。最自然的开发方式是在方法级别声明锁规则,而不是在业务代码中嵌入一堆 tryLock 和 unlock 的样板代码。
2. 灵活的锁 Key 表达 锁的粒度往往由业务参数决定。我们需要一个既能支持 SpEL 动态表达式,又能从方法参数或 DTO 对象的字段注解中自动抽取关键值来组装锁 Key 的方案。
3. 生态兼容性要求 很多团队仍在使用 Spring Boot 2.x,但新项目已转向 Boot 3.x。我们希望一个组件能同时兼容两者,避免维护两套代码或给业务方带来迁移负担。
基于这些目标,组件被设计为具有以下核心特性:
@Lock:定义锁的前缀、分隔符、过期时间、等待策略等。@LockKeyParam 注解的片段与 SpEL 片段按固定顺序拼接,语义清晰。@Enable* 注解,引入依赖即生效。根据你的构建工具选择其一。
Maven
<dependency>
<groupId>io.github.helloworldtang</groupId>
<artifactId>lock-key-param</artifactId>
<version>0.1.0</version>
</dependency>
<dependency>
<groupId>io.github.helloworldtang</groupId>
<artifactId>distributed-lock-redis-spring</artifactId>
<version>0.1.0</version>
</dependency>Gradle (Groovy)dependencies {
implementation 'io.github.helloworldtang:lock-key-param:0.1.0'
implementation 'io.github.helloworldtang:distributed-lock-redis-spring:0.1.0'
}Gradle (Kotlin)dependencies {
implementation("io.github.helloworldtang:lock-key-param:0.1.0")
implementation("io.github.helloworldtang:distributed-lock-redis-spring:0.1.0")
}第2步:配置 Redis确保你的 application.yml 中包含 Redis 连接信息。
# application.yml
spring:
data:
redis:
host: 127.0.0.1
port: 6379第3步:定义 DTO 并标注锁键在需要作为锁标识的字段上添加 @LockKeyParam 注解。
package com.example.dto;
import com.github.chengtang.lockkey.LockKeyParam;
public class OrderRequest {
@LockKeyParam
private Long userId;
@LockKeyParam
private Long orderId;
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
public Long getOrderId() { return orderId; }
public void setOrderId(Long orderId) { this.orderId = orderId; }
}第4步:在 Service 方法上使用 @Lock组件支持两种模式:快速失败 和 自旋等待。
package com.example.service;
import com.github.chengtang.dlock.annotation.Lock;
import com.github.chengtang.dlock.annotation.SpinWaitStrategy;
import com.github.chengtang.dlock.annotation.SpinWaitTimeParam;
import com.github.chengtang.lockkey.LockKeyParam;
import com.example.dto.OrderRequest;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Service
public class OrderService {
private final AtomicInteger calls = new AtomicInteger();
// 快速失败:waitTime=0
@Lock(prefix = "dl", delimiter = ":", expireTime = 5, waitTime = 0, timeUnit = TimeUnit.SECONDS)
public int placeFastFail(OrderRequest req, @LockKeyParam Long orderId) {
try { Thread.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
return calls.incrementAndGet();
}
// 等待窗口:waitTime=2s,线性自旋 interval=100ms
@Lock(
prefix = "dl", delimiter = ":", expireTime = 5, waitTime = 2, timeUnit = TimeUnit.SECONDS,
spinWaitTimeParam = @SpinWaitTimeParam(interval = 100, maxAttempts = 0, strategy = SpinWaitStrategy.LINEAR)
)
public int placeWait(OrderRequest req, @LockKeyParam Long orderId) {
try { Thread.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
return calls.incrementAndGet();
}
}第5步:暴露 API 接口
package com.example.web;
import com.example.dto.OrderRequest;
import com.example.service.OrderService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
private final OrderService service;
public OrderController(OrderService service) { this.service = service; }
@PostMapping("/orders/place-fast")
public int placeFast(@RequestBody OrderRequest req) {
return service.placeFastFail(req, req.getOrderId());
}
@PostMapping("/orders/place-wait")
public int placeWait(@RequestBody OrderRequest req) {
return service.placeWait(req, req.getOrderId());
}
}第6步:运行与验证启动应用后,可以使用 curl、Postman 或编写简单的并发测试进行验证。
快速失败场景:并发发送两个相同 userId 和 orderId 的请求到 /orders/place-fast。预期结果:一个成功(200),另一个因未获取到锁而快速失败(500或自定义异常)。
自旋等待场景:并发发送两个相同请求到 /orders/place-wait。预期结果:两个都成功(200),但第二个请求的响应时间会略有增加(约 0.7s ~ 2s),因为它需要等待第一个请求释放锁。


锁 Key 的 Union All 策略 锁 Key 由两部分按固定顺序拼接而成:
@LockKeyParam 或 DTO 字段上的 @LockKeyParam。@Lock 注解中的 keys 属性,支持完整的 Spring 表达式语言。这种“先注解,后 SpEL”的顺序保证了 Key 组成的可预测性和可读性。
线程安全的令牌管理
每个锁在获取时都会生成一个全局唯一的 UUID 作为令牌。该令牌不仅会存入 Redis,还会保存在调用线程的 ThreadLocal 中。在释放锁时,会通过 Lua 脚本原子性地比较并删除,确保“只有锁的持有者才能释放锁”,杜绝了误解锁的风险。
单包双栈兼容 这是支持 Spring Boot 2.x 和 3.x 的关键。实现原理是:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中声明自动配置类。META-INF/spring.factories 文件中声明自动配置类。
组件通过条件化装配(@ConditionalOnClass 等)确保只在存在 StringRedisTemplate 等必要 Bean 时才生效。可扩展的自旋策略
内置了三种等待策略,可通过 SpinWaitStrategy 指定:
将组件发布到中央仓库,使其能被全球开发者直接引用,是项目成熟的重要标志。以下是经过实战验证的 SOP(标准作业程序)和常见坑点。
io.github.你的用户名 作为 GroupId 的请求会很快被批准。
Tips:可以使用github账号登录sonatype。


没有gpg签名,会报上面的错“Missing signature”
settings.xml在 ~/.m2/settings.xml 中配置 Sonatype 的账号和 GPG 密码。
https://central.sonatype.com/publishing/deployments



图中的username和password,会在下面的settings.xml文件用到
<settings>
<servers>
<server>
<id>central</id>
<username>你的Sonatype用户名</username>
<password>你的Sonatype密码(建议使用Token)</password>
</server>
</servers>
<profiles>
<profile>
<id>gpg</id>
<properties>
<gpg.executable>gpg</gpg.executable>
<gpg.passphrase>你的GPG私钥密码</gpg.passphrase>
</properties>
</profile>
</profiles>
<activeProfiles>
<activeProfile>gpg</activeProfile>
</activeProfiles>
</settings>项目父POM需要集成 central-publishing-maven-plugin 并配置 release 环境。发布时,通常只发布核心库模块,跳过示例模块。
mvn -s /path/to/settings.xml \
-Dmaven.repo.local=~/.m2/repository \
-Prelease -DskipTests \
-pl lock-key-param,distributed-lock-redis-spring -am deploy参数说明:-Prelease:激活发布配置。-pl:指定要部署的模块列表。-am:同时构建这些模块所依赖的模块。Namespace not allowed
问题:部署失败,提示 GroupId 未授权。
解决:确保 pom.xml 中的 groupId 与你在 Sonatype JIRA 上申请并获批的命名空间 完全一致(例如 io.github.helloworldtang)。Missing signature
问题:组件缺少 GPG 签名。
解决:settings.xml 中 GPG 配置正确。maven-gpg-plugin 插件在 release 流程中被正确执行。mvn verify -Prelease。409 Conflict 或构件已存在
问题:相同版本的构件已经发布到 Central,无法覆盖。
解决:Maven Central 不允许覆盖已发布的版本。如果需要更新,必须升级版本号(如 0.1.0 -> 0.1.1)。Activity 面板查看发布状态,通常会经历 PUBLISHING -> Published。

@Lock 注解的属性名(如 waitTime, spinWaitTimeParam)应尽可能直观,成为使用文档的一部分。expireTime),防止业务异常导致锁永不释放。DEBUG 级别详细输出锁的获取、重试、释放日志和最终拼接的 Key,这在复杂并发场景的调试中至关重要。settings.xml 配置)写成详细的脚本或文档,能极大降低下次发布的心智负担和出错率。希望通过本文,你不仅获得了一个开箱即用的分布式锁工具,更能理解其背后的设计思路,并掌握将一个优秀组件交付给整个 Java 社区的标准方法。在分布式系统的世界里,好的工具和清晰的模式,能让复杂问题变得简单。
如果你在实现或发布过程中有任何问题,欢迎PR或留言讨论。