IT虾米网

Springboot2本地锁实践详解

itxm 2019年10月08日 编程语言 748 0

  在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,我们在开发中必须防止表单重复提交….

下面我们利用自定义注解Spring AopGuava 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

第一次请求结果:

第二次请求结果:

 

发布评论

分享到:

IT虾米网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!

Springboot分布式限流实践详解
你是第一个吃螃蟹的人
发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。