基于注解的redis分布式锁的简单实现

651 阅读2分钟

分布式锁的应用场景还是比较多的,然后手头的项目没有有效的封装,然后忙里偷闲,简单封装了一下

分布式锁的实现方式还是很多,比较多的肯定就是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表达式可以直接解析,这样使用就很便利了。