最近因为业务需要用到加锁,所以就想用redis锁,因为对于业务来说,redis锁已经能够满足需求了。
但是,因为需要很多地方需要用到加锁,项目又是基于springboot,所以,就想写个springboot-starter,然后封装个注解,需要的项目中只要引入starter,并且在需要加锁的方法上加上注解就可以了。
1、添加pom依赖
org.springframework.boot
spring-boot-autoconfigure
org.springframework.boot
spring-boot-starter-aop
org.springframework.boot
spring-boot-starter-data-redis
org.apache.commons
commons-lang3
org.apache.commons
commons-pool2
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的生成)
*/
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
4、定义redis锁切面
@Slf4j
@Aspect
public class DistributedRedisLockAspect {
private DistributedRedisLock distributedRedisLock;
public DistributedRedisLockAspect(DistributedRedisLock distributedRedisLock){
this.distributedRedisLock = distributedRedisLock;
}
@Pointcut("@annotation(com.springboot.starter.redis.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 = pjp.getArgs();
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, redisLock.expire(), redisLock.retryTimes(), redisLock.retryInterval());
if(!lock) {
log.error("get lock failed : " + key);
if(redisLock.errorStrategy().equals(RedisLock.ErrorStrategy.THROW_EXCEPTION)){
throw new RedisLockException("Get redis lock failed");
}
return null;
}
log.info("get lock success : {}" ,key);
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();
Assert.notEmpty(Arrays.asList(index), "ArgsIndex is empty");
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
*/
private String getObjectPropertiesKey(RedisLock redisLock, Object[] args) throws NoSuchFieldException, IllegalAccessException {
String[] properties = redisLock.properties();
List keylist = new ArrayList<>(properties.length);
// 可以通过className获取args的位置
Map classNamesArgsIndex = getClassNameArgsIndex(args);
// 可以通过className获取Class类型
Map> classNameClass = getClassNameClass(args);
for (String ppts : properties) {
String[] classProperties = StringUtils.split(ppts, ".");
String className = classProperties[0];
String propertiesName = classProperties[1];
Object argObject = args[classNamesArgsIndex.get(className)];
Class> clazz = classNameClass.get(className);
Field field = clazz.getDeclaredField(propertiesName);
field.setAccessible(true);
Object object = field.get(argObject);
keylist.add(object);
}
return StringUtils.join(keylist.toArray());
}
/**
* 获取类名和参数位置的对应关系
* @param args 所有参数
* @return Map<类名, 参数位置>
*/
private Map getClassNameArgsIndex(Object[] args){
int len = args.length;
Map nameIndex = new HashMap<>();
for(int i = 0; i < len; i++){
String name = StringUtils.substringAfterLast(args[i].getClass().toString(), ".");
nameIndex.put(name, i);
}
return nameIndex;
}
/**
* 获取类名和类的对应关系
* @param args 所有参数
* @return Map<类名, 类>
*/
private Map> getClassNameClass(Object[] args){
int len = args.length;
Map> nameClass = new HashMap<>();
for(int i = 0; i < len; i++){
Class> clazz = args[i].getClass();
String name = StringUtils.substringAfterLast(clazz.toString(), ".");
nameClass.put(name, clazz);
}
return nameClass;
}
5、定义springboot的autoConfiguration配置类
@Slf4j
@Configuration
public class ReidsLockAutoConfiguration {
@Bean
public DistributedRedisLock distributedRedisLock(RedisTemplate redisTemplate){
log.info("init Distributed Redis Lock");
return new DistributedRedisLock(redisTemplate);
}
@Bean
public DistributedRedisLockAspect distributedRedisLockAspect(DistributedRedisLock distributedRedisLock){
log.info("init Distributed Redis Lock Aspect");
return new DistributedRedisLockAspect(distributedRedisLock);
}
}
6、在resources/META-INF/下添加spring.facotries文件
这样一个可以加redis锁注解的springboot-starter就封装完成了,只要引入该starter,配置上redis连接地址,就可以通过@RedisLock() 进行加锁了。