rouyi backend development documentation – idempotence (preventing repeated submissions)

1. What is a duplicate submission

For example, if the user quickly double-clicks a button, the front-end does not disable the button, resulting in two repeated requests being sent.

Implementation Principle

Its implementation principle is very simple. Methods targeting the same parameters can only be executed once within a period of time. The execution process is as follows:

① Before the method is executed, check whether it exists based on the Key corresponding to the parameter.

  • If exists, it means it is being executed and an error will be reported.
  • If not is used, the Key corresponding to the parameter is calculated, stored in Redis, and the expiration time is set, that is, the mark is being executed.

The calculation rules for the Redis Key of the default parameters are implemented by DefaultIdempotentKeyResolver (opens new window), using MD5 (method name + method parameters) to avoid the Redis Key being too long.

② After the method execution is completed, the Key corresponding to the parameter will not be actively deleted.

In essence, the idempotent features provided by the idempotent package are essentially distributed locks implemented based on Redis.

③ If the method execution takes a long time and exceeds the expiration time of the Key, Redis will automatically delete the corresponding Key. Therefore, it is necessary to make a rough assessment to avoid the execution time of the method exceeding the expiration time.

@Idempotent annotation

Declared on a method, it means that the method needs to enable idempotence. code show as below:

// UserController.java

@Idempotent(timeout = 10, timeUnit = TimeUnit.SECONDS, message = "Adding users, please do not submit again")
@PostMapping("/user/create")
public String createUser(User user){
    userService.createUser(user);
    return "Added successfully";
}

When the interface is called again, it is intercepted by idempotence and execution fails.

{
  "code": 900,
  "data": null,
  "msg": "Repeat request, please try again later"
}

Implementation code:

image.png

The corresponding AOP aspect is IdempotentAspect (opens new window):

IdempotentAspect

The prefix of the corresponding Redis Key is idempotent:%s, which can be seen in the IdempotentRedisDAO (opens new window) class, as shown in the following figure:

IdempotentRedisDAO Storage

redis storage example:

image.png

Extension

Parser interface:

/**
 * Idempotent Key parser interface
 *
 * @author taro source code
 */
public interface IdempotentKeyResolver {

    /**
     * Parse a Key
     *
     * @param idempotent idempotent annotation
     * @param joinPoint AOP aspect
     * @return Key
     */
    String resolver(JoinPoint joinPoint, Idempotent idempotent);

}

Default parser:

/**
 * The default idempotent Key parser uses method name + method parameters to assemble a Key
 *
 * In order to avoid the Key being too long, use MD5 for "compression"
 *
 * @author taro source code
 */
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 expression based parser:

/**
 * Based on Spring EL expressions,
 *
 * @author taro source code
 */
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) {
        // Get the list of intercepted method parameter names
        Method method = getMethod(joinPoint);
        Object[] args = joinPoint.getArgs();
        String[] parameterNames = this.parameterNameDiscoverer.getParameterNames(method);
        // Prepare the context for Spring EL expression parsing
        StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
        if (ArrayUtil.isNotEmpty(parameterNames)) {
            for (int i = 0; i < parameterNames.length; i + + ) {
                evaluationContext.setVariable(parameterNames[i], args[i]);
            }
        }

        // Parse parameters
        Expression expression = expressionParser.parseExpression(idempotent.keyArg());
        return expression.getValue(evaluationContext, String.class);
    }

    private static Method getMethod(JoinPoint point) {
        // Handle the situation declared on the class
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        if (!method.getDeclaringClass().isInterface()) {
            return method;
        }

        // Handle the situation declared on the interface
        try {
            return point.getTarget().getClass().getDeclaredMethod(
                    point.getSignature().getName(), method.getParameterTypes());
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

}

What is the difference between these two parsers?

  1. DefaultIdempotentKeyResolver :
    • Default implementation: DefaultIdempotentKeyResolver is the default key resolver and is the simplest implementation.
    • Parsing method: It uses the method name and method parameters to form a string, and then hashes it through MD5 to generate unique key information.
    • Fixed logic: Its parsing logic is fixed and cannot be customized according to specific business needs. It is suitable for idempotent operations that do not require complex critical information logic.
  2. ExpressionIdempotentKeyResolver :
    • Custom implementation: ExpressionIdempotentKeyResolver allows developers to use Spring EL expressions to customize how key information is generated.
    • Parsing method: It dynamically generates key information based on the idempotent.keyArg() expression. Developers can use method parameters, return values, or other contextual information in expressions.
    • Flexibility: It is very flexible, allowing complex key information generation logic to be defined based on specific needs. This is useful in situations where different key information needs to be generated based on different methods or business scenarios.

In summary, DefaultIdempotentKeyResolver is a simple and fixed implementation, suitable for situations where complex key information generation logic is not required. The ExpressionIdempotentKeyResolver is more flexible and allows the generation of key information to be customized according to specific needs. This is very useful for idempotent operations that require dynamic generation of key information. Developers can choose the appropriate parser based on the specific situation.

Example of using ExpressionIdempotentKeyResolver

Here is a simple example using ExpressionIdempotentKeyResolver:

First, assume you have a service class that contains an idempotent operation:

@Service
public class OrderService {

    @Idempotent(keyResolver = ExpressionIdempotentKeyResolver.class, keyArg = "#userId + '-' + #productId")
    public void createOrder(int userId, int productId) {
        //Business logic for creating orders
    }
}

In the above example, the createOrder method is marked as @Idempotent and uses ExpressionIdempotentKeyResolver as the key resolver, specifying a Spring EL expression #userId + '-' + #productId as keyArg.

Here, we use Spring EL expressions to dynamically generate key information to ensure that for each different combination of userId and productId, a different idempotent key is generated. In this way, even if the same user and product try to call the createOrder method multiple times, only the first call will take effect, and subsequent calls will be intercepted by the idempotent control mechanism.

rouyi back-end development document address: Idempotence (prevention of repeated submissions) | ruoyi-vue-pro development guide

The knowledge points of the article match the official knowledge files, and you can further learn related knowledge. Java Skill TreeHomepageOverview 138001 people are learning the system