前言
定义自定义注解
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,转载请注明。