跳到主要内容

一个注解实现Redis分布式锁

借助 Redisson 简化实现分布式锁。

实现效果

  1. 在方法上添加自定义注解,当借助 SpringBoot 代理调用该方法的时候自动加锁和释放锁。
  2. 注解的 key 支持 SpEL

实现原理

借助 Spring 的 AOP 实现。

实现过程及关键代码

添加 AOP 依赖

部分 spring boot starter 已经添加了该依赖,如果找不到相关注解则并未添加 AOP 相关依赖,需自行添加。

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>

自定义锁注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Lock {

/** redis 锁 */
RedisLock lock();

/** redis 锁的 key 后缀(支持 SpEL) */
String suffix();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadLock {
/** redis 锁 */
RedisLock lock();

/** redis 锁的 key 后缀 */
String prefix();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface WriteLock {
/** redis 锁 */
RedisLock lock();

/** redis 锁的 key 后缀 */
String prefix();
}

定义 AOP 切面

@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class RedisLockAspect {
private final SpelExpressionParser parser = new SpelExpressionParser();
private final ParameterNameDiscoverer parameterNameDiscoverer =
new DefaultParameterNameDiscoverer();

private final ApplicationContext applicationContext;
private final RedissonTemplate redissonTemplate;

@Around("@annotation(lock)")
public Object lock(ProceedingJoinPoint joinPoint, Lock lock) throws Throwable {
RedisLock redisLock = lock.lock();
String prefix = parseSpel(lock.prefix(), joinPoint);
AtomicReference<Object> result = new AtomicReference<>();
redissonTemplate.lock(
redisLock,
prefix,
() -> {
try {
Object proceed = joinPoint.proceed();
result.set(proceed);
} catch (Throwable e) {
throw new RuntimeException(e);
}
});
return result.get();
}

@Around("@annotation(readLock)")
public Object readLock(ProceedingJoinPoint joinPoint, ReadLock readLock) throws Throwable {
RedisLock redisLock = readLock.lock();
String prefix = parseSpel(readLock.prefix(), joinPoint);
AtomicReference<Object> result = new AtomicReference<>();
redissonTemplate.readLock(
redisLock,
prefix,
() -> {
try {
Object proceed = joinPoint.proceed();
result.set(proceed);
} catch (Throwable e) {
throw new RuntimeException(e);
}
});
return result.get();
}

@Around("@annotation(writeLock)")
public Object readLock(ProceedingJoinPoint joinPoint, WriteLock writeLock) throws Throwable {
RedisLock redisLock = writeLock.lock();
String prefix = parseSpel(writeLock.prefix(), joinPoint);
AtomicReference<Object> result = new AtomicReference<>();
redissonTemplate.writeLock(
redisLock,
prefix,
() -> {
try {
Object proceed = joinPoint.proceed();
result.set(proceed);
} catch (Throwable e) {
throw new RuntimeException(e);
}
});
return result.get();
}

private String parseSpel(String spel, ProceedingJoinPoint joinPoint)
throws NoSuchMethodException {
var method = ((org.aspectj.lang.reflect.MethodSignature) joinPoint.getSignature()).getMethod();
var context =
new MethodBasedEvaluationContext(
joinPoint.getTarget(), method, joinPoint.getArgs(), parameterNameDiscoverer);
// 注册方法,可以使用 #concatSuffix 调用 ConcatUtil 类中的 concat 方法
context.registerFunction("concatSuffix", ConcatUtil.class.getMethod("concat", Object[].class));
// 注册 bean 的处理器,可以在 suffix 的 SpEL 中调用 bean 的相关方法
context.setBeanResolver(new BeanFactoryResolver(applicationContext));
return parser.parseExpression(spel).getValue(context, String.class);
}
}
ConcatUtil.java
public class ConcatUtil {
public static String concat(Object... args) {
StringBuilder sb = new StringBuilder();
for (Object arg : args) {
sb.append(ObjectUtil.isEmpty(arg) ? "null" : arg);
}
return sb.toString();
}
}

启用 AOP

在启动类上添加注解:@EnableAspectJAutoProxy(exposeProxy = true)

使用示例

提示

在同一个 Bean 里面调用加锁的方法的时候需要获取当前bean的代理对象,通过代理对象调用加锁的方法才能自动加分布式锁:

DistributedLockService service = (DistributedLockService) AopContext.currentProxy();
service.distributedLock(1L);
@Service
public class DistributedLockService {

@Lock(lock = RedisLock.REDIS_LOCK, suffix = "#id")
public void distributedLock(Long id) {
}

@Lock(lock = RedisLock.REDIS_LOCK, suffix = "#concatSuffix(#left, #right)")
public void distributedLockConcat(String left, String right) {
}

@Lock(lock = RedisLock.REDIS_LOCK, suffix = "#entity.getId()")
public void distributedLockGetId(Entity entity) {
}

@Lock(lock = RedisLock.REDIS_LOCK, suffix = "@entityService.getDistributedLockKeyById(#entity.getId())")
public void distributedLockBean(Entity entity) {
}
}