分布式锁的应用场景还是比较多的,然后手头的项目没有有效的封装,然后忙里偷闲,简单封装了一下
分布式锁的实现方式还是很多,比较多的肯定就是redis和zk了,现有的项目只有redis,所以以下的分布式锁是基于redis实现的了。直接上代码吧
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisLock {
// 支持SPEL表达式和普通的key
String[] keys();
// 等待获取锁的时间
long timeout() default 3000L;
}
/**
* 开启redis分布式锁注解
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@ImportAutoConfiguration({LockAop.class})
public @interface EnableRedisLock {
}
@Order(Integer.MIN_VALUE)
@Aspect
public class LockAop {
private static Logger logger = LoggerFactory.getLogger(LockAop.class);
public LockAop(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
logger.info("LockAop 初始化完成!");
}
@Pointcut("@annotation(com.bosssoft.nontax3.saas.billCollection.collect.api.common.anno.RedisLock)")
public void pointCut() {
}
private RedissonClient redissonClient;
//获取被拦截方法参数名列表(使用Spring支持类库) ASM机制 故定义为常量
private static final LocalVariableTableParameterNameDiscoverer U = new LocalVariableTableParameterNameDiscoverer();
//使用SPEL进行key的解析 线程安全所以可复用
private static final ExpressionParser PARSER = new SpelExpressionParser();
@Around("pointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
RLock lock = null;
boolean flag = false;
try {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
RedisLock redisLock = method.getAnnotation(RedisLock.class);
final String[] keys = redisLock.keys();
final long timeout = redisLock.timeout();
if (keys.length > 0) {
final String prefix = getPrefix(keys, method, point.getArgs());
lock = redissonClient.getLock(prefix);
final long l = System.currentTimeMillis();
while (true) {
// 尝试获取锁,获取失败 等待一会
flag = lock.tryLock();
if (flag) {
break;
}
// 等待一会 不要尝试那么快
sleep();
final long j = System.currentTimeMillis();
if (j - l > timeout) {
break;
}
}
}
if (flag) {
return point.proceed();
} else {
throw new BusinessException(LOCK_ERROR);
}
} finally {
if (flag && lock != null) {
try {
if (lock.isLocked()) {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
} catch (Exception e) {
logger.error("lock解锁失败", e);
}
}
}
}
private void sleep() {
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
logger.error("中断异常", e);
}
}
private String getPrefix(String[] keys, Method method, Object[] args) {
StringBuilder builder = new StringBuilder();
for (String key : keys) {
if (key.startsWith("#")) {
builder.append(parseKey(key, method, args)).append(":");
} else {
builder.append(key).append(":");
}
}
return builder.toString();
}
/**
* 获取spel的具体值
*
* @return 具体的前缀
*/
private String parseKey(String key, Method method, Object[] args) {
if (StringUtils.isEmpty(key)) {
return null;
}
String[] paraNameArr = U.getParameterNames(method);
//SPEL上下文
StandardEvaluationContext context = new StandardEvaluationContext();
//把方法参数放入SPEL上下文中
for (int i = 0; i < Objects.requireNonNull(paraNameArr).length; i++) {
context.setVariable(paraNameArr[i], args[i]);
}
return PARSER.parseExpression(key).getValue(context, String.class);
}
只实现了一些基本功能,然后使用了Spring的spel表达式,接来下来看看使用吧
@RedisLock(keys = {"#sortTransfer.agencyIdCode","#sortTransfer.directoryCode","save"})
public void saveData(SortTransfer sortTransfer) {
//Do something、、、
}
spel表达式可以直接解析,这样使用就很便利了。