SpringBoot
是为了简化 Spring
应用的创建、运行、调试、部署等一系列问题而诞生的产物,自动装配的特性让我们可以更好的关注业务本身而不是外部的XML配置,我们只需遵循规范,引入相关的依赖就可以轻易的搭建出一个 WEB 工程
在 一起来学SpringBoot | 第二十二篇:轻松搞定重复提交(一) 一文中介绍了单机版的重复提交解决方案,在如今这个分布式与集群横行的世道中,那怎么够用呢,所以本章重点来了....
重复提交(分布式)
单机版中我们用的是Guava Cache
,但是这玩意存在集群的时候就凉了,所以我们还是要借助类似Redis
、ZooKeeper
之类的中间件实现分布式锁。
本章目标
利用 自定义注解
、Spring Aop
、Redis Cache
实现分布式锁,你想锁表单锁表单,想锁接口锁接口….
具体代码
也很简单…
导入依赖
在 pom.xml
中添加上 starter-web
、starter-aop
、starter-data-redis
的依赖即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <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>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies>
|
属性配置
在 application.properites
资源文件中添加 redis
相关的配置项
1 2 3
| spring.redis.host=localhost spring.redis.port=6379 spring.redis.password=battcn
|
CacheLock 注解
创建一个 CacheLock
注解,本章内容都是实战使用过的,所以属性配置会相对完善了,话不多说注释都给各位写齐全了….
prefix:
缓存中 key 的前缀expire:
过期时间,此处默认为 5 秒timeUnit:
超时单位,此处默认为秒delimiter:
key 的分隔符,将不同参数值分割开来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| package com.battcn.annotation;
import java.lang.annotation.*; import java.util.concurrent.TimeUnit;
|
CacheParam 注解
上一篇中给说过 key 的生成规则是自己定义的,如果通过表达式语法自己得去写解析规则还是比较麻烦的,所以依旧是用注解的方式…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.battcn.annotation;
import java.lang.annotation.*;
|
Key 生成策略(接口)
创建一个 CacheKeyGenerator
具体实现由使用者自己去注入
Key 生成策略(实现)
解析过程虽然看上去优点绕,但认真阅读或者调试就会发现,主要是解析带 CacheLock
注解的属性,获取对应的属性值,生成一个全新的缓存 Key
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| package com.battcn.interceptor;
import com.battcn.annotation.CacheLock; import com.battcn.annotation.CacheParam; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils;
import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Parameter;
|
Lock 拦截器(AOP)
熟悉 Redis
的朋友都知道它是线程安全的,我们利用它的特性可以很轻松的实现一个分布式锁,如 opsForValue().setIfAbsent(key,value)
它的作用就是如果缓存中没有当前 Key 则进行缓存同时返回 true
反之亦然;当缓存后给 key
在设置个过期时间,防止因为系统崩溃而导致锁迟迟不释放形成死锁; 那么我们是不是可以这样认为当返回 true
我们认为它获取到锁了,在锁未释放的时候我们进行异常的抛出….
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| package com.battcn.interceptor;
import com.battcn.annotation.CacheLock; import com.battcn.utils.RedisLockHelper; 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.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.util.StringUtils;
import java.lang.reflect.Method; import java.util.UUID;
|
RedisLockHelper
通过封装成 API 方式调用,灵活度更加高
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
| package com.battcn.utils;
import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisStringCommands; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.types.Expiration; import org.springframework.util.StringUtils;
import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern;
|