以下是代码分析:

package com.cfpay.collection.module.idempotent.aop;

import com.cfpay.collection.common.constant.ErrorCodeConstants;
import com.cfpay.collection.common.constant.RedisConstants;
import com.cfpay.collection.common.exception.GlobalException;
import com.cfpay.collection.common.util.AssertUtils;
import com.cfpay.collection.common.util.CollectionUtils;
import com.cfpay.collection.framework.redis.core.RedisCache;
import com.cfpay.collection.module.idempotent.annotation.Idempotent;
import com.cfpay.collection.module.idempotent.config.IdempotentProperties;
import com.cfpay.collection.module.idempotent.resolver.IdempotentKeyResolver;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import java.util.List;
import java.util.Map;

/**
 * 拦截声明了 {@link Idempotent} 注解的方法,实现幂等操作
 *
 * @author:gaofv
 * @date:2023/7/8 14:27
 */
@Aspect
@Slf4j
public class IdempotentAspect {

    private final Map<Class<? extends IdempotentKeyResolver>, IdempotentKeyResolver> keyResolvers;
    private IdempotentProperties properties;
    private RedisCache redisCache;

    public IdempotentAspect(List<IdempotentKeyResolver> keyResolvers, RedisCache redisCache, IdempotentProperties properties) {
        this.keyResolvers = CollectionUtils.convertMap(keyResolvers, IdempotentKeyResolver::getClass);
        this.redisCache = redisCache;
        this.properties = properties;
    }

    @Before('@annotation(idempotent)')
    public void beforePointCut(JoinPoint joinPoint, Idempotent idempotent) {
        // 获得 IdempotentKeyResolver
        IdempotentKeyResolver keyResolver = keyResolvers.get(idempotent.keyResolver());
        AssertUtils.notNull(keyResolver, '找不到对应的 IdempotentKeyResolver');
        // 解析 Key
        KeyValue keyValue = keyResolver.resolver(joinPoint, idempotent);
        String redisKey = String.format(RedisConstants.IDEMPOTENT_KEY, keyValue.getKey());
        // 获取timeout和timeUnit
        Long timeout = idempotent.timeout();
        if (timeout.longValue() < 0){
            timeout = properties.getTimeout();
        }
        // 锁定 Key
        boolean success = redisCache.setIfAbsent(redisKey, keyValue.getValue(), timeout, idempotent.timeUnit());
        // 锁定失败,抛出异常
        if (!success) {
            log.info('[beforePointCut][方法({}) 参数({}) 存在重复请求]', joinPoint.getSignature().toString(), joinPoint.getArgs());
            throw new GlobalException(ErrorCodeConstants.REPEATED_REQUESTS.getCode(), idempotent.message());
        }
    }
}

代码分析:

  1. 包名和类名: 该类位于com.cfpay.collection.module.idempotent.aop包下,名为IdempotentAspect
  2. 导入依赖: 代码引入了必要的类和注解,包括用于定义切面的@Aspect注解、用于日志记录的@Slf4j注解、用于定义切入点的@Before注解,以及用于处理幂等操作的Idempotent注解等。
  3. 属性声明: 该类定义了三个私有属性:keyResolvers用于存储IdempotentKeyResolver的实现类,properties用于获取配置信息,redisCache用于操作 Redis 缓存。
  4. 构造函数: 构造函数接收IdempotentKeyResolver列表、RedisCache对象和IdempotentProperties对象,并初始化相应的属性。
  5. 切入点定义: @Before('@annotation(idempotent)') 用于定义切入点,即拦截所有标注了@Idempotent注解的方法。
  6. 前置通知方法: beforePointCut方法是在被拦截的方法执行之前执行的。该方法的逻辑如下:
    • 获取IdempotentKeyResolver实例:根据@Idempotent注解中指定的keyResolver属性,从keyResolvers Map 中获取相应的IdempotentKeyResolver实现类。
    • 解析Key:调用IdempotentKeyResolverresolver方法解析出用于标识请求的 Key。
    • 构造 Redis Key:根据解析出的 Key 生成一个 Redis Key,用于在 Redis 中进行加锁操作。
    • 设置超时时间:获取@Idempotent注解中指定的超时时间,如果未指定则使用配置文件中的默认超时时间。
    • 在 Redis 中加锁:使用redisCachesetIfAbsent方法尝试在 Redis 中加锁。如果成功则返回 true,否则返回 false,表示存在重复请求。
    • 抛出异常:如果加锁失败,说明存在重复请求,此时抛出GlobalException异常,返回自定义错误信息。

总结:

这段代码实现了一个简单的基于 AOP 和 Redis 的幂等性拦截器,它可以有效防止重复请求,提高系统的健壮性。

优化建议:

  • 可以考虑添加更多类型的 IdempotentKeyResolver 实现类,以支持更加灵活的 Key 解析方式。
  • 可以将IdempotentProperties对象配置为 Spring Bean,方便管理。
  • 可以将redisCache对象配置为 Spring Bean,方便注入和管理。
  • 可以将日志级别设置为 DEBUG 或 TRACE,方便调试和排查问题。
  • 可以考虑使用更加健壮的锁机制,例如 Redisson,以避免由于 Redis 宕机导致的锁失效问题。
Java 幂等性实现:基于 AOP 和 Redis 的幂等性拦截器

原文地址: http://www.cveoy.top/t/topic/fsDP 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录