Springboot2.x AOP 实现缓存锁,分布式锁

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

 [[410877]]

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.  
  3. import java.lang.annotation.*; 
  4.  
  5. /** 
  6.  * @ClassName:${} 
  7.  * @Description:TODO 
  8.  * @author:xx@163.com 
  9.  * @Date
  10.  */ 
  11. @Target(ElementType.METHOD) 
  12. @Retention(RetentionPolicy.RUNTIME) 
  13. @Documented 
  14. @Inherited 
  15. public @interface GuaveLock 
  16.  
  17.     String key() default ""
  18.  
  19.     /** 
  20.      * 过期时间 TODO 由于用的 guava 暂时就忽略这属性吧 集成 redis 需要用到 
  21.      * 
  22.      * @author fly 
  23.      */ 
  24.     int expire() default 5; 

AOP的运用

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

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 的分隔符,将不同参数值分割开来
  1. package com.ouyue.xiwenapi.annotation; 
  2.  
  3. import java.lang.annotation.*; 
  4. import java.util.concurrent.TimeUnit; 
  5.  
  6. /** 
  7.  * @ClassName:${} 
  8.  * @Description:TODO 
  9.  * @author:xx@163.com 
  10.  * @Date
  11.  */ 
  12. @Target(ElementType.METHOD) 
  13. @Retention(RetentionPolicy.RUNTIME) 
  14. @Documented 
  15. @Inherited 
  16. public @interface RedisLock { 
  17.     /** 
  18.      * redis 锁key的前缀 
  19.      * 
  20.      * @return redis 锁key的前缀 
  21.      */ 
  22.     String prefix() default ""
  23.  
  24.     /** 
  25.      * 过期秒数,默认为5秒 
  26.      * 
  27.      * @return 轮询锁的时间 
  28.      */ 
  29.     int expire() default 5; 
  30.  
  31.     /** 
  32.      * 超时时间单位 
  33.      * 
  34.      * @return 秒 
  35.      */ 
  36.     TimeUnit timeUnit() default TimeUnit.SECONDS; 
  37.  
  38.     /** 
  39.      * <p>Key的分隔符(默认 :)</p> 
  40.      * <p>生成的Key:N:SO1008:500</p> 
  41.      * 
  42.      * @return String 
  43.      */ 
  44.     String delimiter() default ":"

CacheParam 注解

  1. package com.ouyue.xiwenapi.annotation; 
  2.  
  3. import java.lang.annotation.*; 
  4.  
  5. /** 
  6.  * @ClassName:${} 
  7.  * @Description:TODO 
  8.  * @author:xx@163.com 
  9.  * @Date
  10.  */ 
  11. @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD}) 
  12. @Retention(RetentionPolicy.RUNTIME) 
  13. @Documented 
  14. @Inherited 
  15. public @interface CacheParam { 
  16.     /** 
  17.      * 字段名称 
  18.      * 
  19.      * @return String 
  20.      */ 
  21.     String name() default ""

Key 生成策略

  1. package com.ouyue.xiwenapi.componet; 
  2.  
  3. import org.aspectj.lang.ProceedingJoinPoint; 
  4.  
  5. public interface CacheKeyGenerator { 
  6.     /** 
  7.      * 获取AOP参数,生成指定缓存Key 
  8.      * 
  9.      * @param pjp PJP 
  10.      * @return 缓存KEY 
  11.      */ 
  12.     String getLockKey(ProceedingJoinPoint pjp); 

Key 生成策略(实现)

  1. package com.ouyue.xiwenapi.service; 
  2.  
  3. import com.ouyue.xiwenapi.annotation.CacheParam; 
  4. import com.ouyue.xiwenapi.annotation.RedisLock; 
  5. import com.ouyue.xiwenapi.componet.CacheKeyGenerator; 
  6. import org.aspectj.lang.ProceedingJoinPoint; 
  7. import org.aspectj.lang.reflect.MethodSignature; 
  8. import org.springframework.util.ReflectionUtils; 
  9. import org.springframework.util.StringUtils; 
  10.  
  11. import java.lang.annotation.Annotation; 
  12. import java.lang.reflect.Field; 
  13. import java.lang.reflect.Method; 
  14. import java.lang.reflect.Parameter; 
  15.  
  16. /** 
  17.  * @ClassName:${} 
  18.  * @Description:TODO 
  19.  * @author:xx@163.com 
  20.  * @Date
  21.  */ 
  22. public class LockKeyGenerator implements CacheKeyGenerator { 
  23.     @Override 
  24.     public String getLockKey(ProceedingJoinPoint pjp) { 
  25.         MethodSignature signature = (MethodSignature) pjp.getSignature(); 
  26.         Method method = signature.getMethod(); 
  27.         RedisLock lockAnnotation = method.getAnnotation(RedisLock.class); 
  28.         final Object[] args = pjp.getArgs(); 
  29.         final Parameter[] parameters = method.getParameters(); 
  30.         StringBuilder builder = new StringBuilder(); 
  31.         // TODO 默认解析方法里面带 CacheParam 注解的属性,如果没有尝试着解析实体对象中的 
  32.         for (int i = 0; i < parameters.length; i++) { 
  33.             final CacheParam annotation = parameters[i].getAnnotation(CacheParam.class); 
  34.             if (annotation == null) { 
  35.                 continue
  36.             } 
  37.             builder.append(lockAnnotation.delimiter()).append(args[i]); 
  38.         } 
  39.         if (StringUtils.isEmpty(builder.toString())) { 
  40.             final Annotation[][] parameterAnnotations = method.getParameterAnnotations(); 
  41.             for (int i = 0; i < parameterAnnotations.length; i++) { 
  42.                 final Object object = args[i]; 
  43.                 final Field[] fields = object.getClass().getDeclaredFields(); 
  44.                 for (Field field : fields) { 
  45.                     final CacheParam annotation = field.getAnnotation(CacheParam.class); 
  46.                     if (annotation == null) { 
  47.                         continue
  48.                     } 
  49.                     field.setAccessible(true); 
  50.                     builder.append(lockAnnotation.delimiter()).append(ReflectionUtils.getField(field, object)); 
  51.                 } 
  52.             } 
  53.         } 
  54.         return lockAnnotation.prefix() + builder.toString(); 
  55.     } 

Lock 拦截器(AOP)

 

  1. package com.ouyue.xiwenapi.config; 
  2.  
  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.annotation.Around; 
  7. import org.aspectj.lang.annotation.Aspect; 
  8. import org.aspectj.lang.reflect.MethodSignature; 
  9. import org.springframework.beans.factory.annotation.Autowired; 
  10. import org.springframework.context.annotation.Configuration; 
  11. import org.springframework.data.redis.connection.RedisStringCommands; 
  12. import org.springframework.data.redis.core.RedisCallback; 
  13. import org.springframework.data.redis.core.StringRedisTemplate; 
  14. import org.springframework.data.redis.core.types.Expiration; 
  15. import org.springframework.util.StringUtils; 
  16.  
  17. import java.lang.reflect.Method; 
  18.  
  19. /** 
  20.  * @ClassName:${} 
  21.  * @Description:TODO 
  22.  * @author:xx@163.com 
  23.  * @Date
  24.  */ 
  25. @Aspect 
  26. @Configuration 
  27. public class LockMethodInterceptor { 
  28.     @Autowired 
  29.     public LockMethodInterceptor(StringRedisTemplate lockRedisTemplate, CacheKeyGenerator cacheKeyGenerator) { 
  30.         this.lockRedisTemplate = lockRedisTemplate; 
  31.         this.cacheKeyGenerator = cacheKeyGenerator; 
  32.     } 
  33.  
  34.     private final StringRedisTemplate lockRedisTemplate; 
  35.     private final CacheKeyGenerator cacheKeyGenerator; 
  36.  
  37.  
  38.     @Around("execution(public * *(..)) && @annotation(com.ouyue.xiwenapi.annotation.RedisLock)"
  39.     public Object interceptor(ProceedingJoinPoint pjp) { 
  40.         MethodSignature signature = (MethodSignature) pjp.getSignature(); 
  41.         Method method = signature.getMethod(); 
  42.         RedisLock lock = method.getAnnotation(RedisLock.class); 
  43.         if (StringUtils.isEmpty(lock.prefix())) { 
  44.             throw new RuntimeException("lock key don't null..."); 
  45.         } 
  46.         final String lockKey = cacheKeyGenerator.getLockKey(pjp); 
  47.         try { 
  48.             // 采用原生 API 来实现分布式锁 
  49.             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)); 
  50.             if (!success) { 
  51.                 // TODO 按理来说 我们应该抛出一个自定义的 CacheLockException 异常;这里偷下懒 
  52.                 throw new RuntimeException("请勿重复请求"); 
  53.             } 
  54.             try { 
  55.                 return pjp.proceed(); 
  56.             } catch (Throwable throwable) { 
  57.                 throw new RuntimeException("系统异常"); 
  58.             } 
  59.         } finally { 
  60.             // TODO 如果演示的话需要注释该代码;实际应该放开 
  61.             // lockRedisTemplate.delete(lockKey); 
  62.         } 
  63.     } 

请求

  1. package com.ouyue.xiwenapi.controller; 
  2.  
  3. import com.ouyue.xiwenapi.annotation.CacheParam; 
  4. import com.ouyue.xiwenapi.annotation.GuaveLock; 
  5. import com.ouyue.xiwenapi.annotation.RedisLock; 
  6. import org.springframework.web.bind.annotation.GetMapping; 
  7. import org.springframework.web.bind.annotation.RequestMapping; 
  8. import org.springframework.web.bind.annotation.RequestParam; 
  9. import org.springframework.web.bind.annotation.RestController; 
  10.  
  11. /** 
  12.  * @ClassName:${} 
  13.  * @Description:TODO 
  14.  * @author:xx@163.com 
  15.  * @Date
  16.  */ 
  17.  
  18. @RestController 
  19. @RequestMapping("/business"
  20. public class BusinessController { 
  21.     @GuaveLock(key = "business:arg[0]"
  22.     @GetMapping 
  23.     public String query(@RequestParam String token) { 
  24.         return "success - " + token; 
  25.     } 
  26.  
  27.     @RedisLock(prefix = "users"
  28.     @GetMapping 
  29.     public String queryRedis(@CacheParam(name = "token") @RequestParam String token) { 
  30.         return "success - " + token; 
  31.     } 

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

  1. @Bean 
  2. public CacheKeyGenerator cacheKeyGenerator() { 
  3.     return new LockKeyGenerator(); 

 

责任编辑:武晓燕 来源: 今日头条
相关推荐

2023-01-13 07:39:07

2019-02-26 09:51:52

分布式锁RedisZookeeper

2022-01-06 10:58:07

Redis数据分布式锁

2023-08-21 19:10:34

Redis分布式

2021-10-25 10:21:59

ZK分布式锁ZooKeeper

2024-11-28 15:11:28

2018-11-27 16:17:13

分布式Tomcat

2021-11-26 06:43:19

Java分布式

2023-08-27 22:13:59

Redisson分布式缓存

2021-02-28 07:49:28

Zookeeper分布式

2024-07-29 09:57:47

2024-10-07 10:07:31

2023-09-04 08:12:16

分布式锁Springboot

2019-06-19 15:40:06

分布式锁RedisJava

2024-04-01 05:10:00

Redis数据库分布式锁

2018-04-03 16:24:34

分布式方式

2017-04-13 10:51:09

Consul分布式

2017-01-16 14:13:37

分布式数据库

2022-04-08 08:27:08

分布式锁系统

2024-01-02 13:15:00

分布式锁RedissonRedis
点赞
收藏

51CTO技术栈公众号