Spring Boot (32) Lock 本地锁
平时开发中,有时会双击提交表单造成重复提交,或者网速比较慢时还没有响应又点击了按钮,我们在开发中必须防止重复提交
一般在前台进行处理,定义个变量,发送请求前判断变量值为true,然后把变量设置为false,可以防止重复提交问题。如果前台没有做这个控制那就需要后端来处理
Lock 注解
创建一个LocalLock注解,简单一个key就行了,由于暂时还未用到redis所以expire是摆设
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; } }
控制层
在接口上添加@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; } }
启动项目测试
第一次请求
第二次请求