赞
踩
例如说,用户快速的双击了某个按钮,前端没有禁用该按钮,导致发送了两次重复的请求。
它的实现原理非常简单,针对相同参数的方法,一段时间内,有且仅能执行一次。执行流程如下:
① 在方法执行前,根据参数对应的 Key 查询是否存在。
默认参数的 Redis Key 的计算规则由 DefaultIdempotentKeyResolver (opens new window) 实现,使用 MD5(方法名 + 方法参数),避免 Redis Key 过长。
② 方法执行完成,不会主动删除参数对应的 Key。
从本质上来说,idempotent
包提供的幂等特性,本质上也是基于 Redis 实现的分布式锁。
③ 如果方法执行时间较长,超过 Key 的过期时间,则 Redis 会自动删除对应的 Key。因此,需要大概评估下,避免方法的执行时间超过过期时间。
@Idempotent
注解声明在方法上,表示该方法需要开启幂等性。代码如下:
- // UserController.java
-
- @Idempotent(timeout = 10, timeUnit = TimeUnit.SECONDS, message = "正在添加用户中,请勿重复提交")
- @PostMapping("/user/create")
- public String createUser(User user){
- userService.createUser(user);
- return "添加成功";
- }
-
再次调用接口,被幂等性拦截,执行失败。
- {
- "code": 900,
- "data": null,
- "msg": "重复请求,请稍后重试"
- }
-
实现代码:
对应的 AOP 切面是 IdempotentAspect (opens new window) :
对应的 Redis Key 的前缀是 idempotent:%s
,可见 IdempotentRedisDAO (opens new window) 类,如下图所示:
redis存储例子:
解析器接口:
- /**
- * 幂等 Key 解析器接口
- *
- * @author 芋道源码
- */
- public interface IdempotentKeyResolver {
-
- /**
- * 解析一个 Key
- *
- * @param idempotent 幂等注解
- * @param joinPoint AOP 切面
- * @return Key
- */
- String resolver(JoinPoint joinPoint, Idempotent idempotent);
-
- }
默认解析器:
- /**
- * 默认幂等 Key 解析器,使用方法名 + 方法参数,组装成一个 Key
- *
- * 为了避免 Key 过长,使用 MD5 进行“压缩”
- *
- * @author 芋道源码
- */
- public class DefaultIdempotentKeyResolver implements IdempotentKeyResolver {
-
- @Override
- public String resolver(JoinPoint joinPoint, Idempotent idempotent) {
- String methodName = joinPoint.getSignature().toString();
- String argsStr = StrUtil.join(",", joinPoint.getArgs());
- return SecureUtil.md5(methodName + argsStr);
- }
-
- }
基于Spring EL表达式的解析器:
- /**
- * 基于 Spring EL 表达式,
- *
- * @author 芋道源码
- */
- public class ExpressionIdempotentKeyResolver implements IdempotentKeyResolver {
-
- private final ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
- private final ExpressionParser expressionParser = new SpelExpressionParser();
-
- @Override
- public String resolver(JoinPoint joinPoint, Idempotent idempotent) {
- // 获得被拦截方法参数名列表
- 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.keyArg());
- 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);
- }
- }
-
- }
DefaultIdempotentKeyResolver
是默认的键解析器,它是最简单的实现方式。ExpressionIdempotentKeyResolver
允许开发者使用 Spring EL 表达式来自定义关键信息的生成方式。idempotent.keyArg()
表达式来动态生成关键信息。开发者可以在表达式中使用方法参数、返回值或其他上下文信息。总之,DefaultIdempotentKeyResolver
是一个简单而固定的实现,适合不需要复杂关键信息生成逻辑的情况。而 ExpressionIdempotentKeyResolver
则更为灵活,允许根据具体需求自定义关键信息的生成方式,这对于需要动态生成关键信息的幂等操作非常有用。开发人员可以根据具体情况选择合适的解析器。
下面是一个使用 ExpressionIdempotentKeyResolver
的简单示例:
首先,假设有一个服务类,其中包含一个幂等性操作:
- @Service
- public class OrderService {
-
- @Idempotent(keyResolver = ExpressionIdempotentKeyResolver.class, keyArg = "#userId + '-' + #productId")
- public void createOrder(int userId, int productId) {
- // 创建订单的业务逻辑
- }
- }
-
在上述示例中,createOrder
方法被标记为 @Idempotent
,并且使用 ExpressionIdempotentKeyResolver
作为键解析器,同时指定了一个 Spring EL 表达式 #userId + '-' + #productId
作为 keyArg
。
这里,我们使用 Spring EL 表达式来动态生成关键信息,以确保对于每个不同的 userId
和 productId
组合,都会生成不同的幂等性关键。这样,即使相同的用户和产品尝试多次调用 createOrder
方法,只有第一次调用会生效,后续的调用会被幂等性控制机制拦截。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。