一、常见幂等方案
二、幂等方案实现
1、创建幂等注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
/**
* 幂等KEY的超时时间,默认为1秒
*/
int timeout() default 1;
/**
* 时间单位,默认为秒
*/
TimeUnit timeUnit() default TimeUnit.SECONDS;
/**
* 使用的 Key 解析器(可自行扩展)
* @see RequestIdempotentKeyResolver
* @see ExpressionIdempotentKeyResolver
* @see DefaultIdempotentKeyResolver
*/
Class<? extends IdempotentKeyResolver> keyResolver() default DefaultIdempotentKeyResolver.class;
/**
* HTTP请求头中幂等KEY字段名,默认为:IDEMPOTENT_KEY
*/
@AliasFor("express")
String keyParameter() default "";
/**
* Spring SpEL表达式。
* 注意:可用数据为方法的参数
*/
@AliasFor("keyParameter")
String express() default "" ;
}
2、创建幂等切面
@Aspect
@Slf4j
public class IdempotentAspect {
private final Map<Class<? extends IdempotentKeyResolver>, IdempotentKeyResolver> keyResolversMap = new HashMap<>();
private final IdempotentHandler idempotentHandler;
public IdempotentAspect(List<IdempotentKeyResolver> keyResolvers, IdempotentHandler idempotentHandler) {
if(keyResolvers != null){
for(IdempotentKeyResolver resolver : keyResolvers) {
if(resolver == null){
continue;
}
keyResolversMap.put(resolver.getClass(),resolver) ;
}
}
this.idempotentHandler = idempotentHandler;
}
@Before("@annotation(idempotent)")
public void beforePointCut(JoinPoint joinPoint, Idempotent idempotent) {
IdempotentKeyResolver keyResolver = keyResolversMap.get(idempotent.keyResolver());
if(keyResolver == null) {
throw new IdempotentException(ResultCode.IDEMPOTENT_KEY_RESOLVER_NOT_FOUND);
}
String key = keyResolver.resolver(joinPoint, idempotent);
if(StringUtils.isBlank(key)) {
throw new IdempotentException(ResultCode.IDEMPOTENT_KEY_NOT_FOUND);
}
log.info("获取到的幂等KEY:{}",key);
// 进行幂等判断,是否是第一次被执行
boolean success = idempotentHandler.isFirstRun(key, idempotent.timeout(), idempotent.timeUnit());
if (!success) {
throw new IdempotentException(ResultCode.IDEMPOTENT_REPEAT);
}
}
}
3、定义幂等KEY解析器
public interface IdempotentKeyResolver {
/**
* 解析幂等使用的KEY
* @param joinPoint AOP切面
* @param idempotent 幂等注解
*
* @return java.lang.String
*/
String resolver(JoinPoint joinPoint, Idempotent idempotent);
}
4、定义幂等处理器
public interface IdempotentHandler {
/**
* 幂等KEY的奇前缀
*/
static final String IDEMPOTENT_KEY_PREFIX = "IDEMPOTENT:" ;
/**
* 判断是否第一次执行
* @param key 处理幂等操作的唯一键
* @param timeout 幂等操作的过期时间
* @param timeUnit 幂等操作过期时间单位
*
* @return boolean
*/
boolean isFirstRun(String key, long timeout, TimeUnit timeUnit) ;
}
5、创建基于方法签名的幂等KEY解析器
public class DefaultIdempotentKeyResolver implements IdempotentKeyResolver {
@Override
public String resolver(JoinPoint joinPoint,Idempotent idempotent) {
String methodName = joinPoint.getSignature().toString();
String argsStr = JsonUtils.toJsonString(joinPoint.getArgs());
return DigestUtils.md5Hex(methodName + argsStr);
}
}
6、创建基于请求参数的幂等KEY解析器
public class RequestIdempotentKeyResolver implements IdempotentKeyResolver {
/**
* 请求头的默认参数名
*/
public static final String HEADER_NAME = "IDEMPOTENT_KEY" ;
@Override
public String resolver(JoinPoint joinPoint, Idempotent idempotent) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes() ;
if(requestAttributes == null){
return null ;
}
HttpServletRequest request = requestAttributes.getRequest();
String key = HEADER_NAME ;
if(StringUtils.isNotBlank(idempotent.keyParameter())){
key = idempotent.keyParameter() ;
}
String value = request.getHeader(key) ;
if(StringUtils.isNotBlank(value)) {
return value ;
}
return request.getParameter(key) ;
}
}
7、创建基于EL表达式的幂等KEY解析器
public class ExpressionIdempotentKeyResolver implements IdempotentKeyResolver {
private final ParameterNameDiscoverer parameterNameDiscoverer = new StandardReflectionParameterNameDiscoverer();
private final ExpressionParser expressionParser = new SpelExpressionParser();
@Override
public String resolver(JoinPoint joinPoint, Idempotent idempotent) {
if(StringUtils.isBlank(idempotent.express())){
return null ;
}
// 获得被拦截方法参数名列表
Method method = getMethod(joinPoint);
Object[] args = joinPoint.getArgs();
String[] parameterNames = this.parameterNameDiscoverer.getParameterNames(method);
// 准备 Spring EL 表达式解析的上下文
StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
if (ArrayUtil.isNotEmpty(parameterNames)) {
for (int i = 0; i < parameterNames.length; i++) {
evaluationContext.setVariable(parameterNames[i], args[i]);
}
}
// 解析表达式
Expression expression = expressionParser.parseExpression(idempotent.express());
return expression.getValue(evaluationContext, String.class);
}
private static Method getMethod(JoinPoint point) {
// 处理,声明在类上的情况
MethodSignature signature = (MethodSignature)point.getSignature();
Method method = signature.getMethod();
if (!method.getDeclaringClass().isInterface()) {
return method;
}
// 处理,声明在接口上的情况
try {
return point.getTarget().getClass().getDeclaredMethod(point.getSignature().getName(),
method.getParameterTypes());
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}
8、默认的幂等处理器
public class DefaultIdempotentHandler implements IdempotentHandler{
@Override
public boolean isFirstRun(String key, long timeout, TimeUnit timeUnit) {
return false;
}
}
9、基于缓存的幂等处理器
public class CacheIdempotentHandler implements IdempotentHandler {
private CacheService cacheService;
public CacheIdempotentHandler(CacheService cacheService){
this.cacheService = cacheService ;
}
@Override
public boolean isFirstRun(String key, long timeout, TimeUnit timeUnit) {
return cacheService.setIfAbsent(formatKey(key),"",timeout,timeUnit) ;
}
private String formatKey(String key) {
return IDEMPOTENT_KEY_PREFIX + key ;
}
}
10、创建幂等处理器的配置
abstract class HandlerConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(CacheService.class)
@ConditionalOnMissingBean(IdempotentHandler.class)
public static class CacheHandlerConfiguration {
@Bean
private IdempotentHandler idempotentHandler(CacheService cacheService){
return new CacheIdempotentHandler(cacheService) ;
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(IdempotentHandler.class)
public static class DefaultHandlerConfiguration {
@Bean
private IdempotentHandler idempotentHandler(){
return new DefaultIdempotentHandler() ;
}
}
}
11、创建幂等的主配置
@Configuration
public class IdempotentConfiguration {
@Configuration(proxyBeanMethods = false)
@Import({HandlerConfiguration.CacheHandlerConfiguration.class, HandlerConfiguration.DefaultHandlerConfiguration.class})
public class DefaultConfiguration {
@Bean
@ConditionalOnMissingBean(IdempotentAspect.class)
@ConditionalOnBean(IdempotentHandler.class)
public IdempotentAspect idempotentAspect(@Autowired(required = false) List<IdempotentKeyResolver> keyResolvers,
IdempotentHandler idempotentHandler) {
List<IdempotentKeyResolver> keyResolverList = new ArrayList<>();
if (keyResolvers != null && keyResolvers.size() > 0) {
for (IdempotentKeyResolver keyResolver : keyResolvers) {
if (keyResolver == null) {
continue;
}
keyResolverList.add(keyResolver);
}
}
// 添加默认的
keyResolverList.add(new ExpressionIdempotentKeyResolver());
keyResolverList.add(new RequestIdempotentKeyResolver());
keyResolverList.add(new DefaultIdempotentKeyResolver());
return new IdempotentAspect(keyResolverList, idempotentHandler);
}
}
}