您好,登錄后才能下訂單哦!
SpringBoot
?是為了簡化?Spring
?應(yīng)用的創(chuàng)建、運(yùn)行、調(diào)試、部署等一系列問題而誕生的產(chǎn)物,自動(dòng)裝配的特性讓我們可以更好的關(guān)注業(yè)務(wù)本身而不是外部的XML配置,我們只需遵循規(guī)范,引入相關(guān)的依賴就可以輕易的搭建出一個(gè) WEB 工程
在平時(shí)開發(fā)中,如果網(wǎng)速比較慢的情況下,用戶提交表單后,發(fā)現(xiàn)服務(wù)器半天都沒有響應(yīng),那么用戶可能會(huì)以為是自己沒有提交表單,就會(huì)再點(diǎn)擊提交按鈕重復(fù)提交表單,我們?cè)陂_發(fā)中必須防止表單重復(fù)提交….
字面意思就是提交了很多次,這種情況一般都是前端給你挖的坑….
前段時(shí)間在開發(fā)中遇到一個(gè)這樣的問題;前端小哥哥調(diào)用接口的時(shí)候存在?循環(huán)調(diào)用?的問題,正常情況下發(fā)送一個(gè)請(qǐng)求添加一條數(shù)據(jù),結(jié)果變成了同一時(shí)刻并發(fā)的發(fā)送了 N 個(gè)請(qǐng)求,服務(wù)端瞬間懵逼的插入了 N 條一模一樣的數(shù)據(jù),前端小哥哥也不知道問題在哪里(恩...坑就這樣挖好了,反正不填坑,氣死你
) 這時(shí)候咋辦呢;后端干唄,反正臟活累活,背鍋的事情也沒少干了,多一件也不多….
利用?自定義注解
、Spring Aop
、Guava Cache
?實(shí)現(xiàn)表單防重復(fù)提交(不適用于分布式哦,后面會(huì)講分布式方式...
)
非常簡單…
在?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>
創(chuàng)建一個(gè)?LocalLock
?注解,簡單點(diǎn)就一個(gè)?key
?可以了,由于暫時(shí)未用到?redis
?所以?expire
?是擺設(shè)….
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 "";
/**
* 過期時(shí)間 TODO 由于用的 guava 暫時(shí)就忽略這屬性吧 集成 redis 需要用到
*
* @author fly
*/
int expire() default 5;
}
首先通過?CacheBuilder.newBuilder()
?構(gòu)建出緩存對(duì)象,設(shè)置好過期時(shí)間;其目的就是為了防止因程序崩潰鎖得不到釋放(當(dāng)然如果單機(jī)這種方式程序都炸了,鎖早沒了;但這不妨礙我們寫好點(diǎn))
在具體的?interceptor()
?方法上采用的是?Around(環(huán)繞增強(qiáng))
?,所有帶?LocalLock
?注解的都將被切面處理;
如果想更為靈活,key 的生成規(guī)則可以定義成接口形式(可以參考:org.springframework.cache.interceptor.KeyGenerator),這里就偷個(gè)懶了;
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;
/**
* 本章先基于 本地緩存來做,后續(xù)講解 redis 方案
*
* @author Levin
* @since 2018/6/12 0012
*/
@Aspect
@Configuration
public class LockMethodInterceptor {
private static final Cache<String, Object> CACHES = CacheBuilder.newBuilder()
// 最大緩存 100 個(gè)
.maximumSize(1000)
// 設(shè)置寫緩存后 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("請(qǐng)勿重復(fù)請(qǐng)求");
}
// 如果是第一次請(qǐng)求,就將 key 當(dāng)前對(duì)象壓入緩存中
CACHES.put(key, key);
}
try {
return pjp.proceed();
} catch (Throwable throwable) {
throw new RuntimeException("服務(wù)器異常");
} finally {
// TODO 為了演示效果,這里就不調(diào)用 CACHES.invalidate(key); 代碼了
}
}
/**
* key 的生成策略,如果想靈活可以寫成接口與實(shí)現(xiàn)類的方式(TODO 后續(xù)講解)
*
* @param keyExpress 表達(dá)式
* @param args 參數(shù)
* @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]")
;意味著會(huì)將?arg[0]
?替換成第一個(gè)參數(shù)的值,生成后的新 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);
}
}
完成準(zhǔn)備事項(xiàng)后,啟動(dòng)?Chapter21Application
?自行測試即可,測試手段相信大伙都不陌生了,如?瀏覽器
、postman
、junit
、swagger
,此處基于?postman
,如果你覺得自帶的異常信息不夠友好,那么配上巧用SpringBoot輕松搞定全局異常?可以輕松搞定…
第一次請(qǐng)求
第二次請(qǐng)求
本文的重點(diǎn)是你有沒有收獲與成長,其余的都不重要,希望讀者們能謹(jǐn)記這一點(diǎn)。同時(shí)我經(jīng)過多年的收藏目前也算收集到了一套完整的學(xué)習(xí)資料,包括但不限于:分布式架構(gòu)、高可擴(kuò)展、高性能、高并發(fā)、Jvm性能調(diào)優(yōu)、Spring,MyBatis,Nginx源碼分析,Redis,ActiveMQ、Mycat、Netty、Kafka、Mysql、Zookeeper、Tomcat、Docker、Dubbo、Nginx等多個(gè)知識(shí)點(diǎn)高級(jí)進(jìn)階干貨,希望對(duì)想成為架構(gòu)師的朋友有一定的參考和幫助
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。