How to solve repeated commit in pringboot 2.x (practice of local lock)

Posted by abigbluewhale on Wed, 22 Apr 2020 11:22:39 +0200

Have you ever encountered such a situation: the response of the web page is very slow, you find no response after submitting a form, and then you click the submit button crazily (12306 is often so angry). If you have done anti duplicate submission, it's OK, otherwise it's a disaster of what level...

This paper mainly uses custom annotation, spring AOP, and Guava Cache to generate a local lock to achieve the anti duplicate submission effect. Because it is memory based cache, this implementation method is not suitable for distributed services

What is Guava?

guava package is a set of toolkit developed by google, which dislikes the class library of JAVA and is not easy to use. For example, Concurrency, Caches, functional styles, string processing, and so on.

1, Introduce Guava package dependency

<dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>21.0</version>
</dependency>

Two. Custom LocalLock annotation

Customize a LocalLock annotation for methods that need to prevent duplicate commits

/**
 * Comments on locks
 *
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface LocalLock {

    /**
     * @author fly
     */
    String key() default "";
}

After the annotation is defined, the concrete implementation of AOP interceptor facet needs to be done. In the interceptor() method, Around (surround enhancement) is used, and all annotations with LocalLock will be faceted;

Since it's a cache, the next attribute must have an expiration time. Set the expiration time of the cache through expireAfterWrite, and the number of caches by maximumSize.

It is one of the principles of Redis's setNX method to determine whether to commit again by querying whether the key exists in memory.

@Aspect
@Configuration
public class LockMethodInterceptor {

    private static final Cache<String, Object> CACHES = CacheBuilder.newBuilder()
            // Maximum cache 100
            .maximumSize(1000)
            // Expire 5 seconds after setting write cache
            .expireAfterWrite(5, TimeUnit.SECONDS)
            .build();

    @Around("execution(public * *(..)) && @annotation(com.battcn.annotation.LocalLock)")
    public Object interceptor(ProceedingJoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        LocalLock localLock = method.getAnnotation(LocalLock.class);
        String key = getKey(localLock.key(), pjp.getArgs());
        if (!StringUtils.isEmpty(key)) {
            if (CACHES.getIfPresent(key) != null) {
                throw new RuntimeException("Do not repeat request");
            }
            // If it is the first request, the current key object will be pressed into the cache
            CACHES.put(key, key);
        }
        try {
            return pjp.proceed();
        } catch (Throwable throwable) {
            throw new RuntimeException("Server exception");
        } finally {
            // TODO to demonstrate the effect, we will not call cache.invalidate (key); the code
        }
    }

    /**
     * key If you want to be flexible, you can write it as an interface and implementation class (TODO will explain later)
     *
     * @param keyExpress expression
     * @param args       parameter
     * @return Generated key
     */
    private String getKey(String keyExpress, Object[] args) {
        for (int i = 0; i < args.length; i++) {
            keyExpress = keyExpress.replace("arg[" + i + "]", args[i].toString());
        }
        return keyExpress;
    }
}

Implementation of control layer

We add the annotation to the control layer method. key = "city:arg[0] key is defined by itself. arg[0] is the first parameter instead of the matching rule. Then the city:token can't be submitted repeatedly for a certain period of time

@RestController
@RequestMapping("/city")
public class BookController {

    @LocalLock(key = "city:arg[0]")
    @GetMapping
    public String query(@RequestParam String token) {
        return "ok- " + token;
    }
}

test

Let's test it. I use postman

Normal response of the first request

The second time after the request, the result "repeated submission" is returned. Obviously, we have achieved success

Most of the time, we are confused by some high-tech and abstract professional names, which seem to be far away and obscure, but in fact, if you practice it, you will find it very simple!

Endless learning, come on together!

Topics: Programming Google Spring Java Attribute