zoukankan      html  css  js  c++  java
  • Springboot2本地锁实践

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

    下面我们利用自定义注解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 可以了

     1 package com.carry.annotation;
     2 
     3 import java.lang.annotation.Documented;
     4 import java.lang.annotation.ElementType;
     5 import java.lang.annotation.Inherited;
     6 import java.lang.annotation.Retention;
     7 import java.lang.annotation.RetentionPolicy;
     8 import java.lang.annotation.Target;
     9 
    10 /**
    11  * 锁的注解
    12  *
    13  */
    14 @Target(ElementType.METHOD)
    15 @Retention(RetentionPolicy.RUNTIME)
    16 @Documented
    17 @Inherited
    18 public @interface LocalLock {
    19 
    20     String key() default "";
    21 }

     三、Lock拦截器(AOP)

    首先通过 CacheBuilder.newBuilder() 构建出缓存对象,设置好过期时间;其目的就是为了防止因程序崩溃锁得不到释放,然后在具体的 interceptor() 方法上采用的是 Around(环绕增强) ,所有带 LocalLock 注解的都将被切面处理

    具体代码

     1 package com.carry.interceptor;
     2 
     3 import java.lang.reflect.Method;
     4 import java.util.concurrent.TimeUnit;
     5 import org.aspectj.lang.ProceedingJoinPoint;
     6 import org.aspectj.lang.annotation.Around;
     7 import org.aspectj.lang.annotation.Aspect;
     8 import org.aspectj.lang.reflect.MethodSignature;
     9 import org.springframework.context.annotation.Configuration;
    10 import org.springframework.util.StringUtils;
    11 import com.carry.annotation.LocalLock;
    12 import com.google.common.cache.Cache;
    13 import com.google.common.cache.CacheBuilder;
    14 
    15 @Aspect
    16 @Configuration
    17 public class LockMethodInterceptor {
    18 
    19     private static final Cache<String, Object> CACHES = CacheBuilder.newBuilder()
    20             // 最大缓存 100 个
    21             .maximumSize(100)
    22             // 设置写缓存后 5 秒钟过期
    23             .expireAfterWrite(5, TimeUnit.SECONDS).build();
    24 
    25     @Around("execution(public * *(..)) && @annotation(com.carry.annotation.LocalLock)")
    26     public Object interceptor(ProceedingJoinPoint pjp) {
    27         MethodSignature signature = (MethodSignature) pjp.getSignature();
    28         Method method = signature.getMethod();
    29         LocalLock localLock = method.getAnnotation(LocalLock.class);
    30         String key = getKey(localLock.key(), pjp.getArgs());
    31         if (!StringUtils.isEmpty(key)) {
    32             if (CACHES.getIfPresent(key) != null) {
    33                 throw new RuntimeException("请勿重复请求");
    34             }
    35             // 如果是第一次请求,就将 key 当前对象压入缓存中
    36             CACHES.put(key, key);
    37         }
    38         try {
    39             return pjp.proceed();
    40         } catch (Throwable throwable) {
    41             throw new RuntimeException("服务器异常");
    42         } finally {
    43             // TODO
    44         }
    45     }
    46 
    47     /**
    48      * key 的生成策略,如果想灵活可以写成接口与实现类的方式
    49      *
    50      * @param keyExpress
    51      *            表达式
    52      * @param args
    53      *            参数
    54      * @return 生成的key
    55      */
    56     private String getKey(String keyExpress, Object[] args) {
    57         for (int i = 0; i < args.length; i++) {
    58             keyExpress = keyExpress.replace("arg[" + i + "]", args[i].toString());
    59         }
    60         return keyExpress;
    61     }
    62 }

    四、控制层

    在接口方法上添加 @LocalLock(key = "book:arg[0]");意味着会将 arg[0] 替换成第一个参数的值,生成后的新 key 将被缓存起来

    具体代码

     1 package com.carry.controller;
     2 
     3 import org.springframework.web.bind.annotation.GetMapping;
     4 import org.springframework.web.bind.annotation.RequestMapping;
     5 import org.springframework.web.bind.annotation.RequestParam;
     6 import org.springframework.web.bind.annotation.RestController;
     7 
     8 import com.carry.annotation.LocalLock;
     9 
    10 @RestController
    11 @RequestMapping("/test")
    12 public class LocalLockController {
    13 
    14     @LocalLock(key = "key:arg[0]")
    15     @GetMapping
    16     public String query(@RequestParam String token) {
    17         return "success - " + token;
    18     }
    19 }

    五、测试

    启动项目,在postman中输入url:localhost:8080/test?token=1

    第一次请求结果:

    第二次请求结果:

  • 相关阅读:
    165. Compare Version Numbers
    164. Maximum Gap
    3、桶排序
    162. Find Peak Element
    160. Intersection of Two Linked Lists
    155. Min Stack
    154. Find Minimum in Rotated Sorted Array II
    153. Find Minimum in Rotated Sorted Array
    Linux/Unix系统编程手册 第二章:基本概念
    Linux/Unix系统编程手册 第一章:历史和标准
  • 原文地址:https://www.cnblogs.com/carrychan/p/9429159.html
Copyright © 2011-2022 走看看