91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

SpringBoot使用本地鎖搞定重復提交

發布時間:2020-07-28 20:15:54 來源:網絡 閱讀:1250 作者:Java_老男孩 欄目:編程語言

SpringBoot?是為了簡化?Spring?應用的創建、運行、調試、部署等一系列問題而誕生的產物,自動裝配的特性讓我們可以更好的關注業務本身而不是外部的XML配置,我們只需遵循規范,引入相關的依賴就可以輕易的搭建出一個 WEB 工程

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

重復提交

字面意思就是提交了很多次,這種情況一般都是前端給你挖的坑….

前段時間在開發中遇到一個這樣的問題;前端小哥哥調用接口的時候存在?循環調用?的問題,正常情況下發送一個請求添加一條數據,結果變成了同一時刻并發的發送了 N 個請求,服務端瞬間懵逼的插入了 N 條一模一樣的數據,前端小哥哥也不知道問題在哪里(恩...坑就這樣挖好了,反正不填坑,氣死你) 這時候咋辦呢;后端干唄,反正臟活累活,背鍋的事情也沒少干了,多一件也不多….

本章目標

利用?自定義注解Spring AopGuava Cache?實現表單防重復提交(不適用于分布式哦,后面會講分布式方式...

具體代碼

非常簡單…

導入依賴

在?pom.xml?中添加上?spring-boot-starter-web?的依賴即可

<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?可以了,由于暫時未用到?redis?所以?expire?是擺設….

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 攔截器(AOP)

首先通過?CacheBuilder.newBuilder()?構建出緩存對象,設置好過期時間;其目的就是為了防止因程序崩潰鎖得不到釋放(當然如果單機這種方式程序都炸了,鎖早沒了;但這不妨礙我們寫好點)

在具體的?interceptor()?方法上采用的是?Around(環繞增強)?,所有帶?LocalLock?注解的都將被切面處理;

如果想更為靈活,key 的生成規則可以定義成接口形式(可以參考:org.springframework.cache.interceptor.KeyGenerator),這里就偷個懶了;

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;
    }
}

控制層

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

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;
    }
}

主函數

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);

    }
}

測試

完成準備事項后,啟動?Chapter21Application?自行測試即可,測試手段相信大伙都不陌生了,如?瀏覽器postmanjunitswagger,此處基于?postman,如果你覺得自帶的異常信息不夠友好,那么配上巧用SpringBoot輕松搞定全局異常?可以輕松搞定…

第一次請求

SpringBoot使用本地鎖搞定重復提交

第二次請求

SpringBoot使用本地鎖搞定重復提交

本文的重點是你有沒有收獲與成長,其余的都不重要,希望讀者們能謹記這一點。同時我經過多年的收藏目前也算收集到了一套完整的學習資料,包括但不限于:分布式架構、高可擴展、高性能、高并發、Jvm性能調優、Spring,MyBatis,Nginx源碼分析,Redis,ActiveMQ、Mycat、Netty、Kafka、Mysql、Zookeeper、Tomcat、Docker、Dubbo、Nginx等多個知識點高級進階干貨,希望對想成為架構師的朋友有一定的參考和幫助

有需要的可以加一下三千人Java技術交流分享群:“708 701 457”免費獲取

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

宝鸡市| 盐亭县| 孝义市| 大厂| 商洛市| 色达县| 汨罗市| 从化市| 农安县| 巴彦淖尔市| 古蔺县| 通州区| 青川县| 临城县| 重庆市| 镇江市| 咸阳市| 太仆寺旗| 丹江口市| 巴里| 青岛市| 玛沁县| 崇礼县| 大洼县| 四会市| 寿光市| 鹿邑县| 禹州市| 阜南县| 武平县| 定远县| 伊金霍洛旗| 法库县| 永济市| 中西区| 甘孜县| 遂平县| 林西县| 河池市| 化德县| 新津县|