文章基础知识
- Redis、Redisson
- JDK8 特性
- Java异常
- 可重入锁
- SpEL表达式
- 自定义注解
- AOP
- Spring SPI
- Spring Boot Starter
上述知识都比较简单,有不了解的问AI即可
需要的场景
- 分布式系统多节点部署
- 需要使用锁,使用Java内置的锁无法满足实际场景
- 使用可重入锁
目录结构

1. 普通注解
@LockKey
java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Author: Kenan
* @Date: 2025-07-22 07:37
* @Description: 分布式锁
*/
@Target({ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface LockKey {
}
@UseRLock
java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
/**
* @Author: Kenan
* @Date: 2025-07-22 07:37
* @Description: 分布式锁
*/
@Target(ElementType.METHOD) // 仅用于方法
@Retention(RetentionPolicy.RUNTIME) // 运行时保留
public @interface UseRLock {
/**
* 最大超时时间 单位ms
*/
long maxLockTime() default 5000;
TimeUnit maxLockTimeUnit() default TimeUnit.MILLISECONDS;
/**
* lock的area名称,为了区分不同业务
*/
String area() default "default";
/**
* lock的类名称,可自定义设置
* 默认取 package+class
*/
String clazz() default "default";
/**
* lock的method名称,可以自定义设置
* 默认取方法名称
*/
String method() default "default";
/**
* 当policy=LockPolicy.REJECT,异常信息可通过此字段来定义
*/
String errorMsg() default "";
String value() default "默认操作"; // 可配置参数
LockPolicy policy() default LockPolicy.REJECT;
enum LockPolicy {
WAIT, // 等待
REJECT // 存在锁直接拒绝
}
}
@UseRLockAspect
java
import com.scaffold.boot.lock.annonation.LockKey;
import com.scaffold.boot.lock.annonation.UseRLock;
import com.scaffold.boot.lock.exception.RLockException;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.concurrent.TimeUnit;
/**
* @Author: Kenan
* @Date: 2025-07-22 07:44
* @Description: 锁参数切面
*/
@Aspect
@Component
@AllArgsConstructor
public class UseRLockAspect {
private final static String REDIS_NS = "rlock:";
private final static String DEFAULT = "default";
private final Logger log = LoggerFactory.getLogger(this.getClass());
private final RedissonClient redissonClient;
@AfterThrowing(value = "@annotation(com.scaffold.boot.lock.annonation.UseRLock)", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Exception e) throws Throwable {
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
unlock(joinPoint);
} else {
TransactionSynchronizationManager.registerSynchronization(getSynchronization(joinPoint));
}
}
@AfterReturning("@annotation(com.scaffold.boot.lock.annonation.UseRLock)")
public void after(JoinPoint joinPoint) throws Throwable {
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
unlock(joinPoint);
} else {
TransactionSynchronizationManager.registerSynchronization(getSynchronization(joinPoint));
}
}
private TransactionSynchronizationAdapter getSynchronization(JoinPoint joinPoint) {
return new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
try {
unlock(joinPoint);
} catch (Exception ex) {
log.error("unlock:{}", ex.getMessage(), ex);
}
}
};
}
private void unlock(JoinPoint joinPoint) throws NoSuchMethodException {
UseRLock annotation = getUseRLock(joinPoint);
String lockKey = getLockKey(annotation.area(), annotation.clazz(), annotation.method(), joinPoint);
RLock lock = redissonClient.getLock(lockKey);
if (lock == null) {
return;
}
try {
if (lock.isHeldByCurrentThread() && lock.isLocked()) {
lock.unlock();
}
} catch (IllegalMonitorStateException ex) {
log.error("unlock失败:{}", ex.getMessage(), ex);
}
}
@Around("@annotation(com.scaffold.boot.lock.annonation.UseRLock)")
public Object aroundMethod(ProceedingJoinPoint pjp) throws Throwable {
lock(pjp);
return pjp.proceed();
}
private void lock(ProceedingJoinPoint pjp) throws NoSuchMethodException {
UseRLock annotation = getUseRLock(pjp);
TimeUnit maxLockTimeUnit = annotation.maxLockTimeUnit();
long maxLockTime = annotation.maxLockTime();
String area = annotation.area();
String clazz = annotation.clazz();
String method = annotation.method();
UseRLock.LockPolicy policy = annotation.policy();
String lockKey = getLockKey(area, clazz, method, pjp);
RLock lock = redissonClient.getLock(lockKey);
boolean locked = lock.isLocked();
if (locked && UseRLock.LockPolicy.REJECT.equals(policy)) {
String errorMsg = StringUtils.isBlank(annotation.errorMsg()) ? "此业务调用仅允许一个并发处理" : annotation.errorMsg();
throw new RuntimeException(errorMsg);
}
lock.lock(maxLockTime, maxLockTimeUnit);
}
private UseRLock getUseRLock(JoinPoint pjp) throws NoSuchMethodException {
Signature signature = pjp.getSignature();
MethodSignature mSignature = (MethodSignature) signature;
Class<?> declaringType = mSignature.getDeclaringType();
Method method = declaringType.getDeclaredMethod(mSignature.getName(), mSignature.getParameterTypes());
return method.getAnnotation(UseRLock.class);
}
private String getLockKey(String area, String clazz, String methodName, JoinPoint pjp) throws NoSuchMethodException {
Signature signature = pjp.getSignature();
MethodSignature mSignature = (MethodSignature) signature;
Class<?> declaringType = mSignature.getDeclaringType();
Method method = declaringType.getDeclaredMethod(mSignature.getName(), mSignature.getParameterTypes());
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
if (parameterAnnotations.length == 0) {
throw new RLockException("@LockKey must be assigned");
}
if (DEFAULT.equals(clazz)) {
clazz = declaringType.getName();
}
if (DEFAULT.equals(methodName)) {
methodName = method.getName();
}
Parameter[] parameters = method.getParameters();
Object[] args = pjp.getArgs();
StringBuilder lockKey = new StringBuilder();
lockKey.append(REDIS_NS);
lockKey.append(area);
lockKey.append(":");
lockKey.append(clazz);
lockKey.append(":");
lockKey.append(methodName);
boolean hasLockKey = false;
for (int index = 0; index < args.length; index++) {
Parameter parameter = parameters[index];
LockKey annotation = parameter.getAnnotation(LockKey.class);
if (annotation != null) {
lockKey.append(":");
Object arg = args[index];
String argStr = arg.toString();
if (StringUtils.isBlank(argStr)) {
throw new RLockException("@LockKey parameter must not be null");
}
hasLockKey = true;
lockKey.append(argStr);
}
}
if (!hasLockKey) {
throw new RLockException("@LockKey must be assigned");
}
return lockKey.toString();
}
}
Demo
java
@UseRLock(maxLockTime = 1, maxLockTimeUnit = TimeUnit.SECONDS, area = "system", policy = UseRLock.LockPolicy.REJECT)
public Boolean testLock(@LockKey String no, @LockKey String aaa) {
return Boolean.TRUE;
}
2. SpEL方式
@SpELRLock
java
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
/**
* @Author: Kenan
* @Date: 2025-07-22 07:37
* @Description: 分布式锁
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SpELRLock {
/**
* 最大超时时间 单位ms
*/
long maxLockTime() default 5000;
TimeUnit maxLockTimeUnit() default TimeUnit.MILLISECONDS;
/**
* lock的area名称,为了区分不同业务
*/
String area() default "default";
/**
* lock的类名称,可自定义设置
* 默认取 package+class
*/
String clazz() default "default";
/**
* lock的method名称,可以自定义设置
* 默认取方法名称
*/
String method() default "default";
/**
* 当policy=LockPolicy.REJECT,异常信息可通过此字段来定义
*/
String errorMsg() default "";
String value() default "默认操作";
LockPolicy policy() default LockPolicy.REJECT;
/**
* SpEL表达式,用于生成锁的key
* 示例: "#userId" 或 "'order:' + #order.id"
*/
String key();
enum LockPolicy {
WAIT, // 等待
REJECT // 存在锁直接拒绝
}
}
业务代码 @UseRLockAspect
java
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
/**
* @Author: Kenan
* @Date: 2025-07-22 07:37
* @Description: 分布式锁
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SpELRLock {
/**
* 最大超时时间 单位ms
*/
long maxLockTime() default 5000;
TimeUnit maxLockTimeUnit() default TimeUnit.MILLISECONDS;
/**
* lock的area名称,为了区分不同业务
*/
String area() default "default";
/**
* lock的类名称,可自定义设置
* 默认取 package+class
*/
String clazz() default "default";
/**
* lock的method名称,可以自定义设置
* 默认取方法名称
*/
String method() default "default";
/**
* 当policy=LockPolicy.REJECT,异常信息可通过此字段来定义
*/
String errorMsg() default "";
String value() default "默认操作";
LockPolicy policy() default LockPolicy.REJECT;
/**
* SpEL表达式,用于生成锁的key
* 示例: "#userId" 或 "'order:' + #order.id"
*/
String key();
enum LockPolicy {
WAIT, // 等待
REJECT // 存在锁直接拒绝
}
}
Demo
java
@SpELRLock(maxLockTime = 1, maxLockTimeUnit = TimeUnit.SECONDS,
area = "system", policy = SpELRLock.LockPolicy.REJECT,
key = "#no?.toString().concat(':').concat(#userSaveReqVO?.id?.toString())")
public Boolean testSpELLock(String no, UserSaveReqVO userSaveReqVO) {
return Boolean.TRUE;
}
注意事项
- resources目录下的
META-INF.spring
目录结构是上下级的关系,不是同一层级用.
分隔。注入方式从Spring Boot2.7开始为图中的注入方式,以前的版本直接百度即可。
两种方式的对比
- 普通注解解析变量局限在对象中的字段需要额外处理,但是有多个字段共同去使用时则需要在多个字段上标注@LockKey。
- 使用SpEL表达式对不了解此内容的有局限性,容易导致对此内容不了解造成数据异常。但是对函数入口字段没有侵入。
TIPS
- 到官网查到截止2025.1版本都不支持自定义的SpEL表达式,虽然这个需求已经在5年前就有人反馈了...
- 在IDEA的设置中添加SpEL表达式也不会起作用(只能识别到Bean),想要支持的话需要自己开发一个插件
