RedisLock锁注解,基于springboot2.0.4,lettuce
最近因为业务需要⽤到加锁,所以就想⽤redis锁,因为对于业务来说,redis锁已经能够满⾜需求了。
但是,因为需要很多地⽅需要⽤到加锁,项⽬⼜是基于springboot,所以,就想写个springboot-starter,然后封装个注解,需要的项⽬中只要引⼊starter,并且在需要加锁的⽅法上加上注解就可以了。
1、添加pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</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>
<dependency>
<groupId>org.apachemons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apachemons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
2、定义锁注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {
/**
* 锁key
*/
String key() default "";
/**
* key前缀
*/
String prefix() default "";
/**
* 过期时间,单位毫秒
*/
long expire() default 15000;
/**
* 重试次数
*/
int retryTimes() default 0;
/
**
* 重试间隔,单位毫秒
*/
int retryInterval() default 1000;
/**
* 绑定类型(作⽤于key的⽣成)
* 绑定类型(作⽤于key的⽣成)
*/
BindType bindType() default BindType.DEFAULT;
/**
* 绑定参数索引,从0开始,与 bindType.ARGS_INDEX 组合使⽤
*/
int[] bindArgsIndex() default 0;
/**
* 对象参数属性⽰例:ClassName.field, 与bingType.OBJECT_PROPERTIES 组合使⽤    */
String[] properties() default "";
/**
* 失败策略
*/
ErrorStrategy errorStrategy() default ErrorStrategy.THROW_EXCEPTION;
/**
* 参数key绑定类型
*/
enum BindType {
/**
* 默认,将所有参数toString
*/
DEFAULT,
/**
* 参数索引,从0开始
*/
ARGS_INDEX,
/**
* 对象属性
*/
OBJECT_PROPERTIES;
}
/**
* 获取锁失败策略
*/
enum ErrorStrategy {
/**
* 抛异常
*/
THROW_EXCEPTION,
/**
* 返回NULL
*/
RETURN_NULL;
}
}
3、定义加锁逻辑
@Slf4j
public class DistributedRedisLock {
private RedisTemplate<Object, Object> redisTemplate;
private ThreadLocal<String> lockKey = new ThreadLocal<>();
public static final String UNLOCK_LUA;
static {
StringBuilder sb = new StringBuilder();
sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
sb.append("then ");
sb.append("    return redis.call(\"del\",KEYS[1]) ");
sb.append("else ");
sb.append("    return 0 ");
sb.append("end ");
UNLOCK_LUA = sb.toString();
}
public DistributedRedisLock(RedisTemplate<Object, Object> redisTemplate) {
}
/**
* 加锁
* @param key 锁key
* @param expire 过期时间
* @param retryTimes 重试次数
* @param retryInterval 重试间隔
* @return true 加锁成功, false 加锁失败
*/
public boolean lock(String key, long expire, int retryTimes, long retryInterval) {
boolean result = setRedisLock(key, expire);
/**
* 如果获取锁失败,进⾏重试
*/
while((!result) && retryTimes-- > 0){
try {
log.info("lock failed, " + retryTimes);
Thread.sleep(retryInterval);
} catch (InterruptedException e) {
return false;
}
result = setRedisLock(key, expire);
}
return result;
}
/**
* 释放锁
* @param key 锁key
* @return true 释放成功, false 释放失败
*/
public boolean unLock(String key) {
/**
* 释放锁的时候,有可能因为持锁之后⽅法执⾏时间⼤于锁的有效期,此时有可能已经被另外⼀个线程持有锁,所以不能直接删除        * 使⽤lua脚本删除redis中匹配value的key,可以避免由于⽅法执⾏时间过长⽽redis锁⾃动过期失效的时候误删其他线程的锁
*/
try {
RedisCallback<Boolean> callback = (connection) -> {
String value = ();
return connection.eval(Bytes(), ReturnType.BOOLEAN ,1, Bytes(), Bytes());
};
ute(callback);
} catch (Exception e) {
<("release lock occured an exception", e);
} finally {
}
return false;
}
/**
* 设置redis锁
* @param key  锁key
* @param expire 过期时间
* @return true 设置成功,false 设置失败
*/
private boolean setRedisLock(String key, long expire) {
try {
RedisCallback<Boolean> callback = (connection) -> {
String uuid = UUID.randomUUID().toString();
lockKey.set(uuid);
return connection.Bytes(), Bytes(), Expiration.milliseconds(expire), RedisStringCommands.SetOption.SET_IF_ABSENT);            };
ute(callback);
}catch (Exception e){
<("set redis error", e);
}
return false;
}
}
4、定义redis锁切⾯
@Slf4j
@Aspect
public class DistributedRedisLockAspect {
private DistributedRedisLock distributedRedisLock;
public DistributedRedisLockAspect(DistributedRedisLock distributedRedisLock){
this.distributedRedisLock = distributedRedisLock;
}
@Pointcut("@annotation(com.dis.annotation.RedisLock)")
private void redisLockPoint(){}
@Around("redisLockPoint() && @annotation(redisLock)")
public Object around(ProceedingJoinPoint pjp, RedisLock redisLock) throws Throwable {
String key = redisLock.key();
if(StringUtils.isBlank(key)){
Object[] args = Args();
if(redisLock.bindType().equals(RedisLock.BindType.DEFAULT)){
key = StringUtils.join(args);
}else if(redisLock.bindType().equals(RedisLock.BindType.ARGS_INDEX)){
key = getArgsKey(redisLock, args);
}else if(redisLock.bindType().equals(RedisLock.BindType.OBJECT_PROPERTIES)){
key = getObjectPropertiesKey(redisLock, args);
}
}
Assert.hasText(key, "key does not exist");
String prefix = redisLock.prefix();
boolean lock = distributedRedisLock.lock(prefix + key, pire(), Times(), Interval());
if(!lock) {
<("get lock failed : " + key);
Strategy().equals(RedisLock.ErrorStrategy.THROW_EXCEPTION)){
throw new RedisLockException("Get redis lock failed");
}
return null;
}
log.info("get lock success : {}" ,key);
try {
try {
return pjp.proceed();
}finally {
boolean result = distributedRedisLock.unLock(prefix + key);
log.info("release lock : {} {}", prefix + key ,result ? " success" : " failed");
}
}
/**
* 通过绑定的args⽣成key
* @param redisLock redisLock注解
* @param args 所有参数
* @return key
*/
private String getArgsKey(RedisLock redisLock, Object[] args){
int[] index = redisLock.bindArgsIndex();
int len = index.length;
Object[] indexArgs = new Object[index.length];
for(int i = 0; i < len; i++){
indexArgs[i] = args[index[i]];
}
return StringUtils.join(indexArgs);
}
/**
* 通过绑定的对象属性⽣成key
* @param redisLock redisLock注解
* @param args 所有参数
* @return key
springboot aop*/
private String getObjectPropertiesKey(RedisLock redisLock, Object[] args) throws NoSuchFieldException, IllegalAccessException {
String[] properties = redisLock.properties();
List<Object> keylist = new ArrayList<>(properties.length);
// 可以通过className获取args的位置
Map<String, Integer> classNamesArgsIndex = getClassNameArgsIndex(args);
// 可以通过className获取Class类型
Map<String, Class<?>> classNameClass = getClassNameClass(args);
for (String ppts : properties) {
String[] classProperties = StringUtils.split(ppts, ".");
String className = classProperties[0];
String propertiesName = classProperties[1];
Object argObject = (className)];
Class<?> clazz = (className);
Field field = DeclaredField(propertiesName);
field.setAccessible(true);
Object object = (argObject);
keylist.add(object);
}
return StringUtils.Array());
}
/**
* 获取类名和参数位置的对应关系
* @param args 所有参数
* @return Map<;类名, 参数位置>
*/
private Map<String, Integer> getClassNameArgsIndex(Object[] args){
int len = args.length;
Map<String, Integer> nameIndex = new HashMap<>();
for(int i = 0; i < len; i++){

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。