Java 幂等性实现:基于 AOP 和 Redis 的幂等性拦截器
以下是代码分析:
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());
}
}
}
代码分析:
- 包名和类名: 该类位于
com.cfpay.collection.module.idempotent.aop包下,名为IdempotentAspect。 - 导入依赖: 代码引入了必要的类和注解,包括用于定义切面的
@Aspect注解、用于日志记录的@Slf4j注解、用于定义切入点的@Before注解,以及用于处理幂等操作的Idempotent注解等。 - 属性声明: 该类定义了三个私有属性:
keyResolvers用于存储IdempotentKeyResolver的实现类,properties用于获取配置信息,redisCache用于操作 Redis 缓存。 - 构造函数: 构造函数接收
IdempotentKeyResolver列表、RedisCache对象和IdempotentProperties对象,并初始化相应的属性。 - 切入点定义:
@Before('@annotation(idempotent)')用于定义切入点,即拦截所有标注了@Idempotent注解的方法。 - 前置通知方法:
beforePointCut方法是在被拦截的方法执行之前执行的。该方法的逻辑如下:- 获取
IdempotentKeyResolver实例:根据@Idempotent注解中指定的keyResolver属性,从keyResolversMap 中获取相应的IdempotentKeyResolver实现类。 - 解析Key:调用
IdempotentKeyResolver的resolver方法解析出用于标识请求的 Key。 - 构造 Redis Key:根据解析出的 Key 生成一个 Redis Key,用于在 Redis 中进行加锁操作。
- 设置超时时间:获取
@Idempotent注解中指定的超时时间,如果未指定则使用配置文件中的默认超时时间。 - 在 Redis 中加锁:使用
redisCache的setIfAbsent方法尝试在 Redis 中加锁。如果成功则返回 true,否则返回 false,表示存在重复请求。 - 抛出异常:如果加锁失败,说明存在重复请求,此时抛出
GlobalException异常,返回自定义错误信息。
- 获取
总结:
这段代码实现了一个简单的基于 AOP 和 Redis 的幂等性拦截器,它可以有效防止重复请求,提高系统的健壮性。
优化建议:
- 可以考虑添加更多类型的
IdempotentKeyResolver实现类,以支持更加灵活的 Key 解析方式。 - 可以将
IdempotentProperties对象配置为 Spring Bean,方便管理。 - 可以将
redisCache对象配置为 Spring Bean,方便注入和管理。 - 可以将日志级别设置为 DEBUG 或 TRACE,方便调试和排查问题。
- 可以考虑使用更加健壮的锁机制,例如 Redisson,以避免由于 Redis 宕机导致的锁失效问题。
原文地址: http://www.cveoy.top/t/topic/fsDP 著作权归作者所有。请勿转载和采集!