在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,我们在开发中必须防止表单重复提交….
下面我们利用自定义注解
、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
第一次请求结果:
第二次请求结果:
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/17000.html