Springboot2本地锁实践
在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,我们在开发中必须防止表单重复提交….
下面我们利用自定义注解、Spring Aop、Guava 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 可以了
package com.carry.annotation; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* 锁的注解
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface LocalLock { String key() default "";
}
三、Lock拦截器(AOP)
首先通过 CacheBuilder.newBuilder() 构建出缓存对象,设置好过期时间;其目的就是为了防止因程序崩溃锁得不到释放,然后在具体的 interceptor() 方法上采用的是 Around(环绕增强) ,所有带 LocalLock 注解的都将被切面处理
具体代码
package com.carry.interceptor; import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
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 com.carry.annotation.LocalLock;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder; @Aspect
@Configuration
public class LockMethodInterceptor { private static final Cache<String, Object> CACHES = CacheBuilder.newBuilder()
// 最大缓存 100 个
.maximumSize(100)
// 设置写缓存后 5 秒钟过期
.expireAfterWrite(5, TimeUnit.SECONDS).build(); @Around("execution(public * *(..)) && @annotation(com.carry.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
}
} /**
* key 的生成策略,如果想灵活可以写成接口与实现类的方式
*
* @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.carry.controller; 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; import com.carry.annotation.LocalLock; @RestController
@RequestMapping("/test")
public class LocalLockController { @LocalLock(key = "key:arg[0]")
@GetMapping
public String query(@RequestParam String token) {
return "success - " + token;
}
}
五、测试
启动项目,在postman中输入url:localhost:8080/test?token=1
第一次请求结果:

第二次请求结果:

Springboot2本地锁实践的更多相关文章
- SpringBoot2.X最佳实践《一》 之 SpringBoot2.x初体验
SpringBoot2.X最佳实践 前言 本系列文章,从零基础接触 SpringBoot2.x新版本,基础入门使用,热部署,到整合各个主流框架Redis4.x,消息队列AciveMQ, Rocket ...
- SpringBoot2 参数管理实践,入参出参与校验
一.参数管理 在编程系统中,为了能写出良好的代码,会根据是各种设计模式.原则.约束等去规范代码,从而提高代码的可读性.复用性.可修改,实际上个人觉得,如果写出的代码很好,即别人修改也无法破坏原作者的思 ...
- Springboot分布式锁实践(redis)
springboot2本地锁实践一文中提到用Guava Cache实现锁机制,但在集群中就行不通了,所以我们还一般要借助类似Redis.ZooKeeper 之类的中间件实现分布式锁,下面我们将利用自定 ...
- spring-boot-2.0.3之quartz集成,最佳实践
前言 开心一刻 快过年了,大街上,爷爷在给孙子示范摔炮怎么放,嘴里还不停念叨:要像这样,用劲甩才能响.示范了一个,两个,三个... 孙子终于忍不住了,抱着爷爷的腿哭起来:爷呀,你给我剩个吧! 新的一年 ...
- Springboot 2.x 如何解决重复提交 (本地锁的实践)
有没有遇到过这种情况:网页响应很慢,提交一次表单后发现没反应,然后你就疯狂点击提交按钮(12306就经常被这样怒怼),如果做过防重复提交还好,否则那是什么级别的灾难就不好说了... 本文主要是应用 自 ...
- spring-boot-2.0.3之quartz集成,数据源问题,源码探究
前言 开心一刻 着火了,他报警说:119吗,我家发生火灾了. 119问:在哪里? 他说:在我家. 119问:具体点. 他说:在我家的厨房里. 119问:我说你现在的位置. 他说:我趴在桌子底下. 11 ...
- 转: springboot2.0下hystrix.stream 404
springboot2.0下hystrix dashboard Unable to connect to Command Metric Stream解决办法https://blog.csdn.net/ ...
- (转)调优 DB2 UDB v8.1 及其数据库的最佳实践
原文:https://www.ibm.com/developerworks/cn/data/library/techarticles/dm-0404mcarthur/index.html 简介 性能是 ...
- SpringBoot2.x 整合Spring-Session实现Session共享
SpringBoot2.x 整合Spring-Session实现Session共享 1.前言 发展至今,已经很少还存在单服务的应用架构,不说都使用分布式架构部署, 至少也是多点高可用服务.在多个服务器 ...
随机推荐
- getColor()方法过时的替代方法
Android SDK 升級到 23 之後,getResource.getColor(R.color.color_name) 過時 使用新加入的方法 ContextCompat.getColor(co ...
- leetCode(32):Power of Two
Given an integer, write a function to determine if it is a power of two. 2的幂的二进制表示中,必定仅仅有一个"1&q ...
- OC-JS交互(WebViewJavascriptBridge使用说明)
首先确保一份已经配好功能的html文件. 1.初始化一个webview(viewdidload) UIWebView* webView = [[UIWebView alloc] initWithFra ...
- Gradle之依赖管理
Gradle之依赖管理 泡在网上的日子 / 文 发表于2015-01-29 16:12 第8824次阅读 Gradle,Android Studio 2 编辑推荐:稀土掘金,这是一个针对技术开发者的一 ...
- elasticsearch源码分析之search模块(client端)
elasticsearch源码分析之search模块(client端) 注意,我这里所说的都是通过rest api来做的搜索,所以对于接收到请求的节点,我姑且将之称之为client端,其主要的功能我们 ...
- Java中Array、List、Set、Map
一.Java中数组 数组用来存放固定数量的同类元素,声明方法: T[] ref,T ref[],如int[] intAry; int intAry[].推荐用T[]的方式,后一种方式为兼容C++习惯写 ...
- 如何使用github来展示自己的网页
项目文档或者单纯的html页面怎么用github来展示呢? 第一步:新建库 第二步: 上传自己的页面(index.html需在根目录下) 先把git库克隆下来 进入lineShop文件夹,拷贝自己的页 ...
- BZOJ 2427 /HAOI 2010 软件安装 tarjan缩点+树形DP
终于是道中文题了.... 当时考试的时候就考的这道题.... 果断GG. 思路: 因为有可能存在依赖环,所以呢 先要tarjan一遍 来缩点. 随后就进行一遍树形DP就好了.. x表示当前的节点.j表 ...
- Ubuntu 16.04或14.04里下安装搜狗输入法(图文详解)(全网最简单)
不多说,直接上干货! 其实啊,很简单 分三步走 1.添加fcitx的键盘输入法系统,因为sogou是基于fcitx的,而系统默认的是iBus: 2.安装sogou输入法: 3.设置系统参数及一些注意点 ...
- EF Code First 使用 代码优先迁移(二)
第一步:如果不是建立的MVC项目,可能需要在控制台输入 :Install-Package EntityFramework 删除之后在执行Enable-Migrations 第二步:添加你需要修改的属性 ...