Spring Boot (32) Lock 本地鎖

平時開發中,有時會雙擊提交表單形成重複提交,或者網速比較慢時尚未響應又點擊了按鈕,咱們在開發中必須防止重複提交java

通常在前臺進行處理,定義個變量,發送請求前判斷變量值爲true,而後把變量設置爲false,能夠防止重複提交問題。若是前臺沒有作這個控制那就須要後端來處理web

Lock 註解redis

  建立一個LocalLock註解,簡單一個key就好了,因爲暫時還未用到redis因此expire是擺設spring

import java.lang.annotation.*;

//做用於方法
@Target(ElementType.METHOD)
//整個運行階段均可見
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface LocalLock {
    String key() default "";

    /**
     * 過時時間 因爲用的guava 暫時就忽略這個屬性 集成redis時須要用到
     */
    int expire() default 5;
}

Lock攔截器(AOP)後端

  首先經過CacheBuilder.newBuilder()構建出緩存對象,而後設置好過時時間,目的是爲了防止因程序崩潰得不到釋放。緩存

在uti的interceptor()方法上採用的十Around(環繞加強),全部帶LocalLock註解的都將被切面處理,若是想更爲靈活,key的生成規則能夠定義成接口形式。服務器

package com.spring.boot.utils;

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 com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

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

@Aspect
@Configuration
public class LockMethodInterceptor {
    //本地緩存
    private static final Cache<String, Object> CACHES = CacheBuilder.newBuilder()
            //最大1000個
            .maximumSize(1000)
            //設置寫緩存後5秒鐘過時
            .expireAfterAccess(5, TimeUnit.SECONDS)
            .build();

    @Around("execution(public * *(..)) && @annotation(com.spring.boot.utils.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 (key != null || 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 {
            //CACHES.invalidate(key); 完成後移除key
        }
    }

    private String getKey(String key, Object[] args) {
        for (int i = 0; i < args.length; i++) {
            key = key.replace("arg[" + i + "]", args[i].toString());
        }
        return key;
    }
}

控制層app

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

package com.spring.boot.controller;

import com.spring.boot.utils.LocalLock;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

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

啓動項目測試ui

第一次請求

第二次請求

相關文章
相關標籤/搜索