前言
定义自定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {
String key() default ""; // Redis锁的键,使用占位符表示动态参数
String paramName() default ""; // 指定用作动态键的参数名
long expireSeconds() default 15; // 过期时间,单位秒
}
定义环绕方法
@Aspect
@Component
public class RedisLockAspect {
// 注入RedisTemplate,用于与Redis进行交互
@Autowired
private RedisTemplate<String, String> redisTemplate;
// 使用@Around注解,处理带有@RedisLock注解的方法
@Around("@annotation(redisLock)")
public Object handleRedisLock(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
// 从注解中获取锁的键和过期时间
String key = redisLock.key();
long expireSeconds = redisLock.expireSeconds();
// 获取方法上的@RedisLock注解
RedisLock thisRedisLock = getShardingAnnotation(joinPoint);
// 获取动态参数的名字
String paramName = getShardingAnnotation(thisRedisLock,
((MethodSignature) joinPoint.getSignature()).getParameterNames(), joinPoint.getArgs());
// 如果没有指定动态参数名字,或者动态参数值为空,则不加锁
if (StrUtil.isBlank(paramName)) {
return joinPoint.proceed();
}
// 构建锁的键,使用动态参数值替换占位符
String lockKey = String.format(key, paramName);
// 尝试获取锁
Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, "lock", Duration.ofSeconds(expireSeconds));
if (lockAcquired != null && lockAcquired) {
try {
// 锁获取成功,继续执行方法逻辑
return joinPoint.proceed();
} finally {
// 使用Lua脚本释放锁
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), "lock");
}
} else {
// 锁获取失败,可以抛出异常或者根据需要进行处理
throw new DefaultException("数据正在处理中,请勿频繁点击!", ResponseEnum.REQUEST_FREQUENTLY);
}
}
// 通过注解中的参数名获取动态参数的值
private String getShardingAnnotation(RedisLock redisLock, String[] parameterNames, Object[] args) {
String[] properties = redisLock.paramName().split("\\.");
Object obj = null;
for (int i = 0; i < properties.length; i++) {
if (i == 0) {
for (int j = 0; j < parameterNames.length; j++) {
// 找到与动态参数名字匹配的参数值
if (Objects.equals(properties[i], parameterNames[j])) {
obj = args[j];
break;
}
}
} else {
// 通过反射获取嵌套对象的属性值
obj = obj != null ? ReflectUtil.getFieldValue(obj, properties[i]) : null;
}
}
return obj == null ? null : obj.toString();
}
/**
* 获取方法中声明的注解
*
* @param joinPoint
* @return
* @throws NoSuchMethodException
*/
// 通过反射获取方法上的@RedisLock注解
public RedisLock getShardingAnnotation(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
// 获取方法名
String methodName = joinPoint.getSignature().getName();
// 反射获取目标类
Class<?> targetClass = joinPoint.getTarget().getClass();
// 拿到方法对应的参数类型
MethodSignature methodSignature = ((MethodSignature) joinPoint.getSignature());
Class<?>[] parameterTypes = methodSignature.getParameterTypes();
// 根据类、方法、参数类型(重载)获取到方法的具体信息
Method objMethod = targetClass.getMethod(methodName, parameterTypes);
// 拿到方法定义的注解信息
return objMethod.getDeclaredAnnotation(RedisLock.class);
}
}
使用方法
@RedisLock(key = "xxxx:%s", paramName = "xxx.xxx")
本文作者为DBC,转载请注明。