电商项目 - 分布式锁实现 - 声明式

电商项目 - 分布式锁实现 - 声明式

前言

关于分布式锁的相关介绍,这边文章就不多阐述了.可以看上篇文章《电商项目 - 分布式锁实现 - 编程式》原理基本大同小异。

这边文章主要是介绍使用注解+AOP+Redisson实现分布式锁声明式

实现思路

思路: 使用环绕通知,在执行前加锁,执行后释放锁

在这里插入图片描述

代码实现

被@Lock注解标识的方法表示加锁

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface Lock {
    String value();

    String param() default "";

    long waitTime() default 30L;

    long leaseTime() default 100L;

    TimeUnit timeUnit() default TimeUnit.SECONDS;

    LockType type() default LockType.FAIR;
}

Redisson提供了可重入锁、公平锁机制,我们通过LockType来标示

/**
 * 锁类型
 */
public enum LockType {
    /**
     * 可重入锁
     */
    REENTRANT,
    /**
     * 公平锁
     */
    FAIR
}

我们不直接使用Redisson,而是抽象一个接口,由实现类去实现使用Redisson加锁。目的通过面向接口编程实现解耦,下次底层不想使用Redisson作为底层实现可以另写一个类实现接口使用其他底层实现,方便拓展与维护。

public interface RedisLockClient {
    boolean tryLock(String lockName, LockType lockType, long waitTime, long leaseTime, TimeUnit timeUnit) throws InterruptedException;

    void unLock(String lockName, LockType lockType);

    <T> T lock(String lockName, LockType lockType, long waitTime, long leaseTime, TimeUnit timeUnit, CheckedSupplier<T> supplier);

    default <T> T lockFair(String lockName, long waitTime, long leaseTime, CheckedSupplier<T> supplier) {
        return this.lock(lockName, LockType.FAIR, waitTime, leaseTime, TimeUnit.SECONDS, supplier);
    }

    default <T> T lockReentrant(String lockName, long waitTime, long leaseTime, CheckedSupplier<T> supplier) {
        return this.lock(lockName, LockType.REENTRANT, waitTime, leaseTime, TimeUnit.SECONDS, supplier);
    }
}

实现RedisLockClient

@Slf4j
public class RedisLockClientImpl implements RedisLockClient {
    private final RedissonClient redissonClient;

    @Override
    public boolean tryLock(String lockName, LockType lockType, long waitTime, long leaseTime, TimeUnit timeUnit) throws InterruptedException {
        RLock lock = this.getLock(lockName, lockType);
        return lock.tryLock(waitTime, leaseTime, timeUnit);
    }

    @Override
    public void unLock(String lockName, LockType lockType) {
        RLock lock = this.getLock(lockName, lockType);
        if (lock.isLocked() && lock.isHeldByCurrentThread()) {
            lock.unlock();
        }

    }

    private RLock getLock(String lockName, LockType lockType) {
        RLock lock;
        if (LockType.REENTRANT == lockType) {
            lock = this.redissonClient.getLock(lockName);
        } else {
            lock = this.redissonClient.getFairLock(lockName);
        }

        return lock;
    }

    @Override
    public <T> T lock(String lockName, LockType lockType, long waitTime, long leaseTime, TimeUnit timeUnit, CheckedSupplier<T> supplier) {
        T o;
        try {
            boolean result = this.tryLock(lockName, lockType, waitTime, leaseTime, timeUnit);
            if (!result) {
                throw new RuntimeException("业务繁忙!请稍后再试!");
            }          
            o = supplier.get();
        } catch (RuntimeException e) {
            throw e;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            throw new RuntimeException("系统异常");
        } finally {
            this.unLock(lockName, lockType);
        }
        return o;
    }

    public RedisLockClientImpl(final RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }
}

CheckedSupplier用于传递ProceedingJoinPoint

@FunctionalInterface
public interface CheckedSupplier<T> {
    T get() throws Throwable;
}

EL表达式解析器(关于解析EL表达式不熟悉的可以看看《通过AOP+Java注解+EL表达式获取方法参数的值》

public class ExpressionEvaluator<T> extends CachedExpressionEvaluator {
    private final ParameterNameDiscoverer paramNameDiscoverer = new DefaultParameterNameDiscoverer();
    private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<>(64);
    private final Map<AnnotatedElementKey, Method> targetMethodCache = new ConcurrentHashMap<>(64);


    public EvaluationContext createEvaluationContext(Object object, Class<?> targetClass, Method method, Object[] args) {
        Method targetMethod = getTargetMethod(targetClass, method);
        ExpressionRootObject root = new ExpressionRootObject(object, args);
        return new MethodBasedEvaluationContext(root, targetMethod, args, this.paramNameDiscoverer);
    }


    public T condition(String conditionExpression, AnnotatedElementKey elementKey, EvaluationContext evalContext, Class<T> clazz) {
        return getExpression(this.conditionCache, elementKey, conditionExpression).getValue(evalContext, clazz);
    }

    private Method getTargetMethod(Class<?> targetClass, Method method) {
        AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);
        Method targetMethod = this.targetMethodCache.get(methodKey);
        if (targetMethod == null) {
            targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
            if (targetMethod == null) {
                targetMethod = method;
            }
            this.targetMethodCache.put(methodKey, targetMethod);
        }
        return targetMethod;
    }
}
public class ExpressionRootObject {
    private final Object object;
    private final Object[] args;

    public ExpressionRootObject(Object object, Object[] args) {
        this.object = object;
        this.args = args;
    }

    public Object getObject() {
        return object;
    }

    public Object[] getArgs() {
        return args;
    }
}

重点来啦,使用AspectJ的环绕通知来实现加锁/解锁

@Aspect
@Slf4j
@Component
public class RedisLockAspect  {
    private static final ExpressionEvaluator<String> EVALUATOR = new ExpressionEvaluator<>();
    private final RedisLockClient redisLockClient;

    @Around("@annotation(redisLock)")
    public Object aroundRedisLock(ProceedingJoinPoint point, Lock redisLock) {
        log.info("======================into lock=======================");
        String lockName = redisLock.value();
        Assert.hasText(lockName, "@Lock value must have length; it must not be null or empty");
        String lockParam = redisLock.param();
        String lockKey;
        if (StringUtils.isNotBlank(lockParam)) {
            String evalAsText = this.evalLockParam(point, lockParam);
            lockKey = lockName + ':' + evalAsText;
        } else {
            lockKey = lockName;
        }

        LockType lockType = redisLock.type();
        long waitTime = redisLock.waitTime();
        long leaseTime = redisLock.leaseTime();
        TimeUnit timeUnit = redisLock.timeUnit();

        return this.redisLockClient.lock(lockKey, lockType, waitTime, leaseTime, timeUnit, point::proceed);
    }

    /**
     * 解析EL表达式
     * @param point 切入点
     * @param lockParam 需要解析的EL表达式
     * @return 解析出的值
     */
    private String evalLockParam(ProceedingJoinPoint point, String lockParam) {
        MethodSignature ms = (MethodSignature) point.getSignature();
        Method method = ms.getMethod();
        Object[] args = point.getArgs();
        Object target = point.getTarget();
        Class<?> targetClass = target.getClass();
        EvaluationContext context = EVALUATOR.createEvaluationContext(target, target.getClass(), method, args);
        AnnotatedElementKey elementKey = new AnnotatedElementKey(method, targetClass);
        return EVALUATOR.condition(lockParam, elementKey, context, String.class);
    }

    public RedisLockAspect(final RedisLockClient redisLockClient) {
        this.redisLockClient = redisLockClient;
    }
}

最后在需要加锁的方法上加上@Lock即可

@Lock("lock-pay")
@PostMapping("pay")
public ResultVo pay(){
	  // 支付流程
	  return ResultVo.success();
}

以上就完成了分布式锁实现 - 声明式,实现起来并不难,主要得有思路。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值