Springboot2本地鎖實踐


  在平時開發中,如果網速比較慢的情況下,用戶提交表單后,發現服務器半天都沒有響應,那么用戶可能會以為是自己沒有提交表單,就會再點擊提交按鈕重復提交表單,我們在開發中必須防止表單重復提交….

下面我們利用自定義注解Spring AopGuava Cache 實現表單防重復提交

一、導入依賴

 創建springboot項目,在pom.xml文件中加入以下內容

<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注解

創建一個 LocalLock 注解,就一個 key 可以了

 1 package com.carry.annotation;
 2 
 3 import java.lang.annotation.Documented;
 4 import java.lang.annotation.ElementType;
 5 import java.lang.annotation.Inherited;
 6 import java.lang.annotation.Retention;
 7 import java.lang.annotation.RetentionPolicy;
 8 import java.lang.annotation.Target;
 9 
10 /**
11  * 鎖的注解
12  *
13  */
14 @Target(ElementType.METHOD)
15 @Retention(RetentionPolicy.RUNTIME)
16 @Documented
17 @Inherited
18 public @interface LocalLock {
19 
20     String key() default "";
21 }

 三、Lock攔截器(AOP)

首先通過 CacheBuilder.newBuilder() 構建出緩存對象,設置好過期時間;其目的就是為了防止因程序崩潰鎖得不到釋放,然后在具體的 interceptor() 方法上采用的是 Around(環繞增強) ,所有帶 LocalLock 注解的都將被切面處理

具體代碼

 1 package com.carry.interceptor;
 2 
 3 import java.lang.reflect.Method;
 4 import java.util.concurrent.TimeUnit;
 5 import org.aspectj.lang.ProceedingJoinPoint;
 6 import org.aspectj.lang.annotation.Around;
 7 import org.aspectj.lang.annotation.Aspect;
 8 import org.aspectj.lang.reflect.MethodSignature;
 9 import org.springframework.context.annotation.Configuration;
10 import org.springframework.util.StringUtils;
11 import com.carry.annotation.LocalLock;
12 import com.google.common.cache.Cache;
13 import com.google.common.cache.CacheBuilder;
14 
15 @Aspect
16 @Configuration
17 public class LockMethodInterceptor {
18 
19     private static final Cache<String, Object> CACHES = CacheBuilder.newBuilder()
20             // 最大緩存 100 個
21             .maximumSize(100)
22             // 設置寫緩存后 5 秒鍾過期
23             .expireAfterWrite(5, TimeUnit.SECONDS).build();
24 
25     @Around("execution(public * *(..)) && @annotation(com.carry.annotation.LocalLock)")
26     public Object interceptor(ProceedingJoinPoint pjp) {
27         MethodSignature signature = (MethodSignature) pjp.getSignature();
28         Method method = signature.getMethod();
29         LocalLock localLock = method.getAnnotation(LocalLock.class);
30         String key = getKey(localLock.key(), pjp.getArgs());
31         if (!StringUtils.isEmpty(key)) {
32             if (CACHES.getIfPresent(key) != null) {
33                 throw new RuntimeException("請勿重復請求");
34             }
35             // 如果是第一次請求,就將 key 當前對象壓入緩存中
36             CACHES.put(key, key);
37         }
38         try {
39             return pjp.proceed();
40         } catch (Throwable throwable) {
41             throw new RuntimeException("服務器異常");
42         } finally {
43             // TODO
44         }
45     }
46 
47     /**
48      * key 的生成策略,如果想靈活可以寫成接口與實現類的方式
49      *
50      * @param keyExpress
51      *            表達式
52      * @param args
53      *            參數
54      * @return 生成的key
55      */
56     private String getKey(String keyExpress, Object[] args) {
57         for (int i = 0; i < args.length; i++) {
58             keyExpress = keyExpress.replace("arg[" + i + "]", args[i].toString());
59         }
60         return keyExpress;
61     }
62 }

四、控制層

在接口方法上添加 @LocalLock(key = "book:arg[0]");意味着會將 arg[0] 替換成第一個參數的值,生成后的新 key 將被緩存起來

具體代碼

 1 package com.carry.controller;
 2 
 3 import org.springframework.web.bind.annotation.GetMapping;
 4 import org.springframework.web.bind.annotation.RequestMapping;
 5 import org.springframework.web.bind.annotation.RequestParam;
 6 import org.springframework.web.bind.annotation.RestController;
 7 
 8 import com.carry.annotation.LocalLock;
 9 
10 @RestController
11 @RequestMapping("/test")
12 public class LocalLockController {
13 
14     @LocalLock(key = "key:arg[0]")
15     @GetMapping
16     public String query(@RequestParam String token) {
17         return "success - " + token;
18     }
19 }

五、測試

啟動項目,在postman中輸入url:localhost:8080/test?token=1

第一次請求結果:

第二次請求結果:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM