SpringBoot use a local lock to get duplicate submission

SpringBoot To simplify the  Spring creation of applications, run, debug, deploy a range of issues such as the birth of the product, characteristics of the automatic assembly so that we can better focus on the business itself, rather than external XML configuration, we simply follow the norms related to the introduction of dependence can easily build a WEB project

In normal development, if at a relatively slow speed situations, the user submits the form, the discovery server for a long time are not responding, the user may think that he did not submit the form and click the submit button Repeat submit the form, we are developing forms must prevent duplicate submission ....

repeated submit

Literally means to submit a lot of times, this is usually the front to dig your pit ....

Earlier encountered in the development of such a problem; there is a call interface when a front end little brother  cycle call  problem, normally a request to add a data transmission, the result becomes the same timing of the concurrent transmission of the N requests, service end moment ignorant forced to insert the N pieces of exactly the same data, the front end of the small brother did not know where the problem lies ( 恩...坑就这样挖好了,反正不填坑,气死你) this time we supposed to do; the back-end dry chant, anyway, dirty work, scapegoat thing no less dry , not much more than a ....

Chapter Objectives

Use  自定义注解, Spring Aop, Guava Cache to achieve the anti-form repeatedly submit 不适用于分布式哦,后面会讲分布式方式...( )

Specific code

very simple…

Import dependence

In  pom.xml the add  spring-boot-starter-web -dependent can

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>21.0</version>
    </dependency>
</dependencies>

Lock Notes

Create an  LocalLock annotation, a simple point on  key it, due to a temporary unused  redis it  expire is furnished ....

package com.battcn.annotation;

import java.lang.annotation.*;

/**
 * 锁的注解
 *
 * @author Levin
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface LocalLock {

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

    /**
     * 过期时间 TODO 由于用的 guava 暂时就忽略这属性吧 集成 redis 需要用到
     *
     * @author fly
     */
    int expire() default 5;
}

Lock interceptor (AOP)

First, by  CacheBuilder.newBuilder() building a cache object, set the expiration time; its purpose is to prevent crashes lock is not released (of course, if a stand-alone program in this way are blown up, lock early gone; but this does not prevent us write point )

In particular  interceptor() use it is on  Around(环绕增强) all bands  LocalLock annotated section will be processed;

If you want more flexible, key generation rules can be defined as the interface form ( refer to: org.springframework.cache.interceptor.KeyGenerator ), here the lazy;

package com.battcn.interceptor;

import com.battcn.annotation.LocalLock;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * 本章先基于 本地缓存来做,后续讲解 redis 方案
 *
 * @author Levin
 * @since 2018/6/12 0012
 */
@Aspect
@Configuration
public class LockMethodInterceptor {

    private static final Cache<String, Object> CACHES = CacheBuilder.newBuilder()
            // 最大缓存 100 个
            .maximumSize(1000)
            // 设置写缓存后 5 秒钟过期
            .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("请勿重复请求");
            }
            // 如果是第一次请求,就将 key 当前对象压入缓存中
            CACHES.put(key, key);
        }
        try {
            return pjp.proceed();
        } catch (Throwable throwable) {
            throw new RuntimeException("服务器异常");
        } finally {
            // TODO 为了演示效果,这里就不调用 CACHES.invalidate(key); 代码了
        }
    }

    /**
     * key 的生成策略,如果想灵活可以写成接口与实现类的方式(TODO 后续讲解)
     *
     * @param keyExpress 表达式
     * @param args       参数
     * @return 生成的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;
    }
}

Control Layer

Added interface  @LocalLock(key = "book:arg[0]"); means that will  arg[0] replace the value of the first parameter, the new key generation to be cached;

package com.battcn.controller;

import com.battcn.annotation.LocalLock;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * BookController
 *
 * @author Levin
 * @since 2018/6/06 0031
 */
@RestController
@RequestMapping("/books")
public class BookController {

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

The main function

package com.battcn;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author Levin
 */
@SpringBootApplication
public class Chapter21Application {

    public static void main(String[] args) {

        SpringApplication.run(Chapter21Application.class, args);

    }
}

test

Upon completion of preparation matters, start  Chapter21Application self-testing can, I believe everyone testing means are not familiar, such as  浏览器, , postman, junit, swaggerbased here  postman, if you feel that comes with exception information is not friendly enough, then coupled with clever use of SpringBoot easy to get global exception  be easy to get ...

The first request

SpringBoot use a local lock to get duplicate submission

The second request

SpringBoot use a local lock to get duplicate submission

Guess you like

Origin blog.51cto.com/14230003/2416915