|
|
|
@ -27,48 +27,82 @@ import org.springframework.stereotype.Component;
|
|
|
|
|
import java.lang.reflect.Method;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Redis锁切面类
|
|
|
|
|
* 该类利用Spring AOP(面向切面编程)机制,通过定义切面和切点,实现了基于Redis锁的并发控制功能。
|
|
|
|
|
* 主要作用是在被标注了`@RedisLock`注解的方法执行前后进行加锁和解锁操作,以保证在同一时刻只有一个线程能够执行被标注的方法,避免并发冲突。
|
|
|
|
|
*
|
|
|
|
|
* @author lgh
|
|
|
|
|
*/
|
|
|
|
|
@Aspect
|
|
|
|
|
// 使用 @Aspect 注解声明该类是一个切面类,用于定义切点和增强逻辑(如前置通知、后置通知、环绕通知等)
|
|
|
|
|
@Component
|
|
|
|
|
// 使用 @Component 注解将该类注册为Spring容器中的一个组件,方便进行依赖注入等操作
|
|
|
|
|
public class RedisLockAspect {
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
private RedissonClient redissonClient;
|
|
|
|
|
@Autowired
|
|
|
|
|
// 通过依赖注入获取RedissonClient对象,用于与Redis进行交互,操作Redis锁相关的功能
|
|
|
|
|
private RedissonClient redissonClient;
|
|
|
|
|
|
|
|
|
|
// 定义Redis锁的键名前缀,用于在Redis中区分不同的锁,方便管理和识别
|
|
|
|
|
private static final String REDISSON_LOCK_PREFIX = "redisson_lock:";
|
|
|
|
|
|
|
|
|
|
private static final String REDISSON_LOCK_PREFIX = "redisson_lock:";
|
|
|
|
|
/**
|
|
|
|
|
* 环绕通知方法,用于在被标注`@RedisLock`注解的方法执行前后添加加锁和解锁逻辑
|
|
|
|
|
* 该方法在目标方法执行前获取Redis锁,在目标方法执行完毕后释放锁,确保在同一时刻只有一个线程能够执行被标注的方法,实现并发控制。
|
|
|
|
|
*
|
|
|
|
|
* @param joinPoint 连接点对象,代表了目标方法的执行点,通过它可以获取目标方法的相关信息,如方法签名、参数等。
|
|
|
|
|
* @param redisLock 从目标方法上获取的`@RedisLock`注解对象,通过它可以获取注解中定义的锁相关的配置信息,如锁的键表达式、锁名称、过期时间等。
|
|
|
|
|
* @return 返回目标方法执行的结果,即被标注`@RedisLock`注解的方法正常执行后的返回值。
|
|
|
|
|
* @throws Throwable 如果在目标方法执行过程中或者获取、释放锁的过程中出现异常,会将异常向上抛出。
|
|
|
|
|
*/
|
|
|
|
|
@Around("@annotation(redisLock)")
|
|
|
|
|
// 使用 @Around 注解定义环绕通知,该通知会在目标方法执行前后进行额外的逻辑处理,这里就是围绕着被标注`@RedisLock`注解的方法进行加锁和解锁操作
|
|
|
|
|
public Object around(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
|
|
|
|
|
// 获取`@RedisLock`注解中定义的用于生成锁键的SPEL表达式,SPEL表达式可以根据方法参数等动态生成锁键
|
|
|
|
|
String spel = redisLock.key();
|
|
|
|
|
// 获取`@RedisLock`注解中定义的锁名称,用于更直观地标识锁的用途等信息
|
|
|
|
|
String lockName = redisLock.lockName();
|
|
|
|
|
|
|
|
|
|
@Around("@annotation(redisLock)")
|
|
|
|
|
public Object around(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
|
|
|
|
|
String spel = redisLock.key();
|
|
|
|
|
String lockName = redisLock.lockName();
|
|
|
|
|
// 根据连接点、锁名称以及SPEL表达式生成最终的Redis锁的键名,并通过RedissonClient获取对应的Redis锁对象
|
|
|
|
|
RLock rLock = redissonClient.getLock(getRedisKey(joinPoint, lockName, spel));
|
|
|
|
|
|
|
|
|
|
RLock rLock = redissonClient.getLock(getRedisKey(joinPoint,lockName,spel));
|
|
|
|
|
// 使用获取到的Redis锁对象进行加锁操作,设置锁的过期时间和时间单位,按照`@RedisLock`注解中定义的配置来进行
|
|
|
|
|
rLock.lock(redisLock.expire(), redisLock.timeUnit());
|
|
|
|
|
|
|
|
|
|
rLock.lock(redisLock.expire(),redisLock.timeUnit());
|
|
|
|
|
Object result = null;
|
|
|
|
|
try {
|
|
|
|
|
// 执行被标注`@RedisLock`注解的目标方法,即让原本的业务逻辑正常执行
|
|
|
|
|
result = joinPoint.proceed();
|
|
|
|
|
|
|
|
|
|
Object result = null;
|
|
|
|
|
try {
|
|
|
|
|
//执行方法
|
|
|
|
|
result = joinPoint.proceed();
|
|
|
|
|
} finally {
|
|
|
|
|
// 无论目标方法执行是否成功,最终都要释放锁,确保锁资源能够被正确释放,避免死锁等问题
|
|
|
|
|
rLock.unlock();
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
|
rLock.unlock();
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* 将SPEL表达式转换为具体的Redis锁键字符串的方法
|
|
|
|
|
* 该方法根据连接点(包含目标方法、目标对象以及方法参数等信息)、锁名称以及SPEL表达式,通过解析SPEL表达式,结合相关信息生成最终用于在Redis中标识锁的键字符串。
|
|
|
|
|
*
|
|
|
|
|
* @param joinPoint 切点对象,代表了目标方法的执行点,从中可以获取目标方法、目标对象以及方法参数等信息,用于解析SPEL表达式。
|
|
|
|
|
* @param lockName 锁的名称,作为生成的Redis锁键的一部分,用于更直观地标识锁的用途等信息。
|
|
|
|
|
* @param spel 用于生成锁键的SPEL表达式,通过解析该表达式,结合目标方法、目标对象以及方法参数等信息来生成最终的锁键字符串。
|
|
|
|
|
* @return 返回生成的Redis锁键字符串,格式为"redisson_lock:锁名称:具体解析后的表达式内容",用于在Redis中唯一标识一个锁。
|
|
|
|
|
*/
|
|
|
|
|
private String getRedisKey(ProceedingJoinPoint joinPoint, String lockName, String spel) {
|
|
|
|
|
// 获取连接点对应的方法签名信息,用于后续获取目标方法对象
|
|
|
|
|
Signature signature = joinPoint.getSignature();
|
|
|
|
|
// 将方法签名信息转换为方法签名的具体实现类对象,方便获取目标方法的详细信息
|
|
|
|
|
MethodSignature methodSignature = (MethodSignature) signature;
|
|
|
|
|
// 获取目标方法对象,后续可用于解析SPEL表达式等操作
|
|
|
|
|
Method targetMethod = methodSignature.getMethod();
|
|
|
|
|
// 获取目标对象,即被代理的实际业务对象,同样可用于解析SPEL表达式等操作
|
|
|
|
|
Object target = joinPoint.getTarget();
|
|
|
|
|
// 获取目标方法的参数数组,这些参数在解析SPEL表达式时可能会作为变量参与计算
|
|
|
|
|
Object[] arguments = joinPoint.getArgs();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 将spel表达式转换为字符串
|
|
|
|
|
* @param joinPoint 切点
|
|
|
|
|
* @return redisKey
|
|
|
|
|
*/
|
|
|
|
|
private String getRedisKey(ProceedingJoinPoint joinPoint,String lockName,String spel) {
|
|
|
|
|
Signature signature = joinPoint.getSignature();
|
|
|
|
|
MethodSignature methodSignature = (MethodSignature) signature;
|
|
|
|
|
Method targetMethod = methodSignature.getMethod();
|
|
|
|
|
Object target = joinPoint.getTarget();
|
|
|
|
|
Object[] arguments = joinPoint.getArgs();
|
|
|
|
|
return REDISSON_LOCK_PREFIX + lockName + StrUtil.COLON + SpelUtil.parse(target,spel, targetMethod, arguments);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 通过工具类方法解析SPEL表达式,结合锁名称等信息生成最终的Redis锁键字符串,并添加前缀,形成完整的用于在Redis中标识锁的键名
|
|
|
|
|
return REDISSON_LOCK_PREFIX + lockName + StrUtil.COLON + SpelUtil.parse(target, spel, targetMethod, arguments);
|
|
|
|
|
}
|
|
|
|
|
}
|