本网站(662p.com)打包出售,且带程序代码数据,662p.com域名,程序内核采用TP框架开发,需要联系扣扣:2360248666 /wx:lianweikj
精品域名一口价出售:1y1m.com(350元) ,6b7b.com(400元) , 5k5j.com(380元) , yayj.com(1800元), jiongzhun.com(1000元) , niuzen.com(2800元) , zennei.com(5000元)
需要联系扣扣:2360248666 /wx:lianweikj
Springboot2.x AOP 实现缓存锁,分布式锁
奔跑的男人 · 258浏览 · 发布于2021-07-13 +关注

本人深根后台系统多年的经验;用户在网络不好情况下; 在做表单提交时;会出现重复提交的情况;故而我们需要:做到防止表单重提。


Springboot2.x AOP 实现 缓存锁, 分布式锁 防止重复提交

本人深根后台系统多年的经验;用户在网络不好情况下; 在做表单提交时;会出现重复提交的情况;故而我们需要:做到防止表单重提

google的guave cache

  1. <dependency> 

  2.     <groupId>org.springframework.boot</groupId> 

  3.     <artifactId>spring-boot-starter-web</artifactId> 

  4. </dependency> 

  5. <dependency> 

  6.     <groupId>org.springframework.boot</groupId> 

  7.     <artifactId>spring-boot-starter-aop</artifactId> 

  8. </dependency> 

  9. <dependency> 

  10.     <groupId>com.google.guava</groupId> 

  11.     <artifactId>guava</artifactId> 

  12.     <version>21.0</version> 

  13. </dependency> 

注解接口

  1. package com.ouyue.xiwenapi.annotation; 

  2. import java.lang.annotation.*; 

  3. /** 

  4.  * @ClassName:${} 

  5.  * @Description:TODO 

  6.  * @author:xx@163.com 

  7.  * @Date: 

  8.  */ 

  9. @Target(ElementType.METHOD) 

  10. @Retention(RetentionPolicy.RUNTIME) 

  11. @Documented 

  12. @Inherited 

  13. public @interface GuaveLock 

  14.     String key() default ""; 

  15.     /** 

  16.      * 过期时间 TODO 由于用的 guava 暂时就忽略这属性吧 集成 redis 需要用到 

  17.      * 

  18.      * @author fly 

  19.      */ 

  20.     int expire() default 5; 

AOP的运用

  1. package com.ouyue.xiwenapi.config; 

  2. import com.google.common.cache.Cache; 

  3. import com.google.common.cache.CacheBuilder; 

  4. import com.ouyue.xiwenapi.annotation.GuaveLock; 

  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 java.lang.reflect.Method; 

  12. import java.util.concurrent.TimeUnit; 

  13. /** 

  14.  * @ClassName:${} 

  15.  * @Description:TODO 

  16.  * @author:xx@163.com 

  17.  * @Date: 

  18.  */ 

  19. @Aspect 

  20. @Configuration 

  21. public class LockMethodAopConfigure { 

  22.     private static final Cache<String, Object> CACHES = CacheBuilder.newBuilder() 

  23.             // 最大缓存 100 个 

  24.             .maximumSize(1000) 

  25.             // 设置写缓存后 5 秒钟过期 

  26.             .expireAfterWrite(5, TimeUnit.SECONDS) 

  27.             .build(); 

  28.     @Around("execution(public * *(..)) && @annotation(com.ouyue.xiwenapi.annotation.GuaveLock)") 

  29.     public Object interceptor(ProceedingJoinPoint pjp) { 

  30.         MethodSignature signature = (MethodSignature) pjp.getSignature(); 

  31.         Method method = signature.getMethod(); 

  32.         GuaveLock localLock = method.getAnnotation(GuaveLock.class); 

  33.         String key = getKey(localLock.key(), pjp.getArgs()); 

  34.         if (!StringUtils.isEmpty(key)) { 

  35.             if (CACHES.getIfPresent(key) != null) { 

  36.                 throw new RuntimeException("请勿重复请求"); 

  37.             } 

  38.             // 如果是第一次请求,就将 key 当前对象压入缓存中 

  39.             CACHES.put(key, key); 

  40.         } 

  41.         try { 

  42.             return pjp.proceed(); 

  43.         } catch (Throwable throwable) { 

  44.             throw new RuntimeException("服务器异常"); 

  45.         } finally { 

  46.             // TODO 为了演示效果,这里就不调用 CACHES.invalidate(key); 代码了 

  47.         } 

  48.     } 

  49.     /** 

  50.      * key 的生成策略,如果想灵活可以写成接口与实现类的方式(TODO 后续讲解) 

  51.      * 

  52.      * @param keyExpress 表达式 

  53.      * @param args       参数  可以 采用MD5加密成一个 

  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.     } 

Controller

  1. @RestController 

  2. @RequestMapping("/business") 

  3. public class BusinessController { 

  4.     @GuaveLock(key = "business:arg[0]") 

  5.     @GetMapping 

  6.     public String query(@RequestParam String token) { 

  7.         return "success - " + token; 

  8.     } 

上面的基本都是居于内存级别的缓存;在分布式系统上; 是无法满足的;故而我们需要做到分布式系统中;也能使用


基于Redis 缓存锁的实现

pom.xml

  1. <dependency> 

  2.     <groupId>org.springframework.boot</groupId> 

  3.     <artifactId>spring-boot-starter-data-redis</artifactId> 

  4. </dependency> 

  5. spring.redis.host=localhost 

  6. spring.redis.port=6379 

RedisLock

  1. prefix: 缓存中 key 的前缀

  2. expire: 过期时间,此处默认为 5 秒

  3. timeUnit: 超时单位,此处默认为秒

  4. delimiter: key 的分隔符,将不同参数值分割开来

  5. package com.ouyue.xiwenapi.annotation; 

  6. import java.lang.annotation.*; 

  7. import java.util.concurrent.TimeUnit; 

  8. /** 

  9.  * @ClassName:${} 

  10.  * @Description:TODO 

  11.  * @author:xx@163.com 

  12.  * @Date: 

  13.  */ 

  14. @Target(ElementType.METHOD) 

  15. @Retention(RetentionPolicy.RUNTIME) 

  16. @Documented 

  17. @Inherited 

  18. public @interface RedisLock { 

  19.     /** 

  20.      * redis 锁key的前缀 

  21.      * 

  22.      * @return redis 锁key的前缀 

  23.      */ 

  24.     String prefix() default ""; 

  25.     /** 

  26.      * 过期秒数,默认为5秒 

  27.     * 

  28.      * @return 轮询锁的时间 

  29.      */ 

  30.    int expire() default 5; 

  31.     /** 

  32.      * 超时时间单位 

  33.      * 

  34.      * @return 秒 

  35.      */ 

  36.     TimeUnit timeUnit() default TimeUnit.SECONDS; 


  37.     /** 

  38.      * <p>Key的分隔符(默认 :)</p> 

  39.      * <p>生成的Key:N:SO1008:500</p> 

  40.      * 

  41.      * @return String 

  42.      */ 

  43.    String delimiter() default ":"; 

CacheParam 注解

  1. package com.ouyue.xiwenapi.annotation; 

  2. import java.lang.annotation.*; 

  3. /** 

  4.  * @ClassName:${} 

  5.  * @Description:TODO 

  6.  * @author:xx@163.com 

  7.  * @Date: 

  8.  */ 

  9. @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD}) 

  10. @Retention(RetentionPolicy.RUNTIME) 

  11. @Documented 

  12. @Inherited 

  13. public @interface CacheParam { 

  14.     /** 

  15.      * 字段名称 

  16.      * 

  17.      * @return String 

  18.      */ 

  19.     String name() default ""; 

Key 生成策略

  1. package com.ouyue.xiwenapi.componet; 

  2. import org.aspectj.lang.ProceedingJoinPoint; 

  3. public interface CacheKeyGenerator { 

  4.     /** 

  5.      * 获取AOP参数,生成指定缓存Key 

  6.      * 

  7.      * @param pjp PJP 

  8.      * @return 缓存KEY 

  9.      */ 

  10.    String getLockKey(ProceedingJoinPoint pjp); 

Key 生成策略(实现)

  1. package com.ouyue.xiwenapi.service; 

  2. import com.ouyue.xiwenapi.annotation.CacheParam; 

  3. import com.ouyue.xiwenapi.annotation.RedisLock; 

  4. import com.ouyue.xiwenapi.componet.CacheKeyGenerator; 

  5. import org.aspectj.lang.ProceedingJoinPoint; 

  6. import org.aspectj.lang.reflect.MethodSignature; 

  7. import org.springframework.util.ReflectionUtils; 

  8. import org.springframework.util.StringUtils; 

  9. import java.lang.annotation.Annotation; 

  10. import java.lang.reflect.Field; 

  11. import java.lang.reflect.Method; 

  12. import java.lang.reflect.Parameter; 


  13. /** 

  14.  * @ClassName:${} 

  15.  * @Description:TODO 

  16.  * @author:xx@163.com 

  17.  * @Date: 

  18.  */ 

  19. public class LockKeyGenerator implements CacheKeyGenerator { 

  20.     @Override 

  21.     public String getLockKey(ProceedingJoinPoint pjp) { 

  22.         MethodSignature signature = (MethodSignature) pjp.getSignature(); 

  23.         Method method = signature.getMethod(); 

  24.        RedisLock lockAnnotation = method.getAnnotation(RedisLock.class); 

  25.         final Object[] args = pjp.getArgs(); 

  26.         final Parameter[] parameters = method.getParameters(); 

  27.         StringBuilder builder = new StringBuilder(); 

  28.         // TODO 默认解析方法里面带 CacheParam 注解的属性,如果没有尝试着解析实体对象中的 

  29.         for (int i = 0; i < parameters.length; i++) { 

  30.             final CacheParam annotation = parameters[i].getAnnotation(CacheParam.class); 

  31.             if (annotation == null) { 

  32.                 continue; 

  33.             } 

  34.            builder.append(lockAnnotation.delimiter()).append(args[i]); 

  35.         } 

  36.         if (StringUtils.isEmpty(builder.toString())) { 

  37.             final Annotation[][] parameterAnnotations = method.getParameterAnnotations(); 

  38.             for (int i = 0; i < parameterAnnotations.length; i++) { 

  39.                 final Object object = args[i]; 

  40.                 final Field[] fields = object.getClass().getDeclaredFields(); 

  41.                 for (Field field : fields) { 

  42.                     final CacheParam annotation = field.getAnnotation(CacheParam.class); 

  43.                     if (annotation == null) { 

  44.                         continue; 

  45.                     } 

  46.                     field.setAccessible(true); 

  47.                     builder.append(lockAnnotation.delimiter()).append(ReflectionUtils.getField(field, object)); 

  48.                 } 

  49.             } 

  50.         } 

  51.         return lockAnnotation.prefix() + builder.toString(); 

  52.     } 

Lock 拦截器(AOP)

  1. package com.ouyue.xiwenapi.config; 

  2. import com.ouyue.xiwenapi.annotation.RedisLock; 

  3. import com.ouyue.xiwenapi.componet.CacheKeyGenerator; 

  4. import org.aspectj.lang.ProceedingJoinPoint; 

  5. import org.aspectj.lang.annotation.Around; 

  6. import org.aspectj.lang.annotation.Aspect; 

  7. import org.aspectj.lang.reflect.MethodSignature; 

  8. import org.springframework.beans.factory.annotation.Autowired; 

  9. import org.springframework.context.annotation.Configuration; 

  10. import org.springframework.data.redis.connection.RedisStringCommands; 

  11. import org.springframework.data.redis.core.RedisCallback; 

  12. import org.springframework.data.redis.core.StringRedisTemplate; 

  13. import org.springframework.data.redis.core.types.Expiration; 

  14. import org.springframework.util.StringUtils; 

  15. import java.lang.reflect.Method; 

  16. /** 

  17.  * @ClassName:${} 

  18.  * @Description:TODO 

  19.  * @author:xx@163.com 

  20.  * @Date: 

  21.  */ 

  22. @Aspect 

  23. @Configuration 

  24. public class LockMethodInterceptor { 

  25.     @Autowired 

  26.     public LockMethodInterceptor(StringRedisTemplate lockRedisTemplate, CacheKeyGenerator cacheKeyGenerator) { 

  27.         this.lockRedisTemplate = lockRedisTemplate; 

  28.         this.cacheKeyGenerator = cacheKeyGenerator; 

  29.     } 

  30.     private final StringRedisTemplate lockRedisTemplate; 

  31.     private final CacheKeyGenerator cacheKeyGenerator; 

  32.     @Around("execution(public * *(..)) && @annotation(com.ouyue.xiwenapi.annotation.RedisLock)") 

  33.     public Object interceptor(ProceedingJoinPoint pjp) { 

  34.         MethodSignature signature = (MethodSignature) pjp.getSignature(); 

  35.         Method method = signature.getMethod(); 

  36.         RedisLock lock = method.getAnnotation(RedisLock.class); 

  37.         if (StringUtils.isEmpty(lock.prefix())) { 

  38.             throw new RuntimeException("lock key don't null..."); 

  39.         } 

  40.         final String lockKey = cacheKeyGenerator.getLockKey(pjp); 

  41.         try { 

  42.             // 采用原生 API 来实现分布式锁 

  43.             final Boolean success = lockRedisTemplate.execute((RedisCallback<Boolean>) connection -> connection.set(lockKey.getBytes(), new byte[0], Expiration.from(lock.expire(), lock.timeUnit()), RedisStringCommands.SetOption.SET_IF_ABSENT)); 

  44.             if (!success) { 

  45.                 // TODO 按理来说 我们应该抛出一个自定义的 CacheLockException 异常;这里偷下懒 

  46.                 throw new RuntimeException("请勿重复请求"); 

  47.             } 

  48.             try { 

  49.                 return pjp.proceed(); 

  50.             } catch (Throwable throwable) { 

  51.                 throw new RuntimeException("系统异常"); 

  52.             } 

  53.         } finally { 

  54.             // TODO 如果演示的话需要注释该代码;实际应该放开 

  55.             // lockRedisTemplate.delete(lockKey); 

  56.         } 

  57.     } 

请求

  1. package com.ouyue.xiwenapi.controller; 

  2. import com.ouyue.xiwenapi.annotation.CacheParam; 

  3. import com.ouyue.xiwenapi.annotation.GuaveLock; 

  4. import com.ouyue.xiwenapi.annotation.RedisLock; 

  5. import org.springframework.web.bind.annotation.GetMapping; 

  6. import org.springframework.web.bind.annotation.RequestMapping; 

  7. import org.springframework.web.bind.annotation.RequestParam; 

  8. import org.springframework.web.bind.annotation.RestController; 

  9. /** 

  10.  * @ClassName:${} 

  11.  * @Description:TODO 

  12.  * @author:xx@163.com 

  13. * @Date: 

  14.  */ 

  15. @RestController 

  16. @RequestMapping("/business") 

  17. public class BusinessController { 

  18.     @GuaveLock(key = "business:arg[0]") 

  19.     @GetMapping 

  20.     public String query(@RequestParam String token) { 

  21.         return "success - " + token; 

  22.     } 

  23.     @RedisLock(prefix = "users") 

  24.     @GetMapping 

  25.     public String queryRedis(@CacheParam(name = "token") @RequestParam String token) { 

  26.         return "success - " + token; 

  27.     } 

mian 函数启动类上;将key 生产策略函数注入


  1. @Bean 

  2. public CacheKeyGenerator cacheKeyGenerator() { 

  3.     return new LockKeyGenerator(); 


相关推荐

PHP实现部分字符隐藏

沙雕mars · 1312浏览 · 2019-04-28 09:47:56
Java中ArrayList和LinkedList区别

kenrry1992 · 896浏览 · 2019-05-08 21:14:54
Tomcat 下载及安装配置

manongba · 957浏览 · 2019-05-13 21:03:56
JAVA变量介绍

manongba · 953浏览 · 2019-05-13 21:05:52
什么是SpringBoot

iamitnan · 1077浏览 · 2019-05-14 22:20:36
加载中

0评论

评论
分类专栏
小鸟云服务器
扫码进入手机网页