注解式上锁 | 青训营笔记

48 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 7 天

前面实现了使用redis上锁,但这么写似乎还不是特别方便,每次使用都要try catch,注入,重复写代码,那有没有更方便的方式呢?为了解决大项目上锁的需求,我去学习了如何利用注解上锁。

思路分析

利用aop切面编程来实现上锁和解锁的事件,以加了注解的方法为切入点,在方法请求前自动上锁,待方法完成后解锁。锁是基于上一节的redis锁的思路,利用setNx函数的性质实现锁。

代码实现

定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Lock {

    /**
     * key
     * @return
     */
    String key();

    /**
     * 锁过期时间
     * @return
     */
    long ttl() default 10L;

    /**
     * 是否重新请求
     * @return
     */
    boolean restart() default false;

    /**
     * 重新请求的次数
     * @return
     */
    int rCount() default 3;

    /**
     * 每次重新请求的等待间隔
     * @return
     */
    long waitTime() default 200L;
}

这个是上锁的注解,作用在方法上。属性上可以设置锁的key,同时支持SpEL表达式,大大增加了锁的key的灵活性,同时还能设置重新请求等。

注解的实现

@Aspect
@Component
@Slf4j
public class LockAnnotationParser {

    @Resource
    private RedisService redisService;

    /**
     * 定义切点
     */
    @Pointcut(value = "@annotation(net.lab1024.smartadmin.common.anno.Lock)")
    private void cutMethod() {}

    /**
     * 切点实现逻辑
     * @param point
     * @param lock
     * @return
     * @throws Throwable
     */
    @Around(value = "cutMethod() && @annotation(lock)")
    public Object parser(ProceedingJoinPoint point, Lock lock) throws Throwable {
        String key = lock.key();
        boolean restart = lock.restart();
        int rCount = lock.rCount();
        long waitTime = lock.waitTime();
        if (isEl(key)) {
            String[] split = key.split("#");
            key = split[0] + getByEl("#" + split[1], point);
        }
        try {
            boolean tryLock = redisService.tryLock(key, lock.key(), lock.ttl());
            if(tryLock) {
                return point.proceed();
            } else {
                if(restart) {
                    long increment = redisService.increment(key);
                    if(increment <= rCount) {
                        Thread.sleep(waitTime);
                        return parser(point,lock);
                    }
                }
                return ResponseDTO.error("占用中,请稍后再试");
            }
        } finally {
            redisService.delete("icr:" + key);
            redisService.unlock(key);
        }
    }

    /**
     * 解析 SpEL 表达式并返回其值
     */
    private String getByEl(String el, ProceedingJoinPoint point) {
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        String[] paramNames = getParameterNames(method);
        Object[] arguments = point.getArgs();

        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(el);
        EvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < arguments.length; i++) {
            context.setVariable(paramNames[i], arguments[i]);
        }

        return expression.getValue(context, String.class);
    }

    /**
     * 获取方法参数名列表
     */
    private String[] getParameterNames(Method method) {
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        return u.getParameterNames(method);
    }

    private boolean isEl(String str) {
        return str.contains("#");
    }
}

利用aop编程,以加了@Lock的注解为切入点,实现业务逻辑。