乐趣区

关于java:锁分布式redission-SpringAop-SpringEL-OGNL

引言:

非单点利用,JDK 自带的管程锁 (即:监视器锁、Monitor 锁,通过 synchronized 关键字来实现加锁)、或可重入锁(ReentrantLock) 已无奈做到对临界资源的加锁,达到同步拜访的目标。

简介:

分布式锁基于 Redis + SpringAOP 来实现,通过申明式注解的形式为具体临界资源加锁,达到同步拜访的目标。

实现:

RedissionLock 注解类定义:

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

    /**
     * 锁生效工夫
     * 默认 - 1 秒,永恒持有,直到以后锁被开释
     */
    int expireTime() default -1;

    /**
     * 抢锁等待时间,默认 30 秒,- 1 示意不期待,胜利或失败都立刻返回
     */
    int waitTime() default 30;

    /**
     * 锁过期工夫单位,默认秒
     */
    TimeUnit expireTimeUnit() default TimeUnit.SECONDS;

    /**
     * 抢锁等待时间单位,默认秒
     */
    TimeUnit waitTimeUnit() default TimeUnit.SECONDS;

    //--------------------------------------------------------------------
    // redisKey 对 values、expressions、spEL、keyGenerator 进行拼接
    //--------------------------------------------------------------------

    /**
     * redisKey
     */
    String[] values() default {};

    /**
     * redisKey
     * 反对 OGNL 表达式
     */
    String[] expressions() default {};

    /**
     * redisKey
     * Spring Expression Language (SpEL) expression for computing the key dynamically.
     */
    String[] spEL() default {};


    /**
     * The bean name of the custom {@link RedisKeyGenerator} to use.
     */
    String keyGenerator() default "";


    /**
     * 未拿锁时的错误码
     * MSG_1000007(1000007, "零碎忙碌,请稍后再试(key=%s)")
     */
    ErrCode errorCode() default ErrCode.MSG_1000007;

    /**
     * 等锁超时,是否抛出异样
     */
    boolean throwExceptionWhenTryLockTimeOut() default true;}

RedisKeyGenerator 接口定义:

@FunctionalInterface
public interface RedisKeyGenerator {

   /**
    * Generate a key for the given method and its parameters.
    * @param target the target instance
    * @param method the method being called
    * @param params the method parameters (with any var-args expanded)
    * @return a generated key
    */
   String generate(Object target, Method method, Object... params);

}

切面定义:

/**
 * 确保此切面执行程序在事务切面之前。事务切面执行程序是 {@link Integer#MAX_VALUE}
 */
@Slf4j
@Aspect
@Order(0)
@Component
public class LockAop implements ApplicationContextAware {

    @Resource
    private RedissonClient redissonClient;

    @Pointcut("@annotation(RedissonLock)")
    private void pointCut() {}

    private static final String prefix = "c:e:lock:";

    @Around(value = "pointCut() && @annotation(redissonLock)")
    public Object around(ProceedingJoinPoint joinPoint, RedissonLock redissonLock) throws Throwable {

        String redisKey;

        // 字符串
        StringJoiner joiner = new StringJoiner(":", prefix, StringUtils.EMPTY);
        String[] values = redissonLock.values();
        if (ArrayUtils.isNotEmpty(values)) {for (String value : values) {joiner.add(value);
            }
        }

        // OGNL 表达式
        String[] expressions = redissonLock.expressions();
        if (ArrayUtils.isNotEmpty(expressions)) {
            // 根对象
            Map<String, Object> rootObject = getVariables(joinPoint);
            for (String expression : expressions) {joiner.add(String.valueOf(OgnlCache.getValue(expression, rootObject)));
            }
        }

        // Spring EL 表达式
        String[] spEL = redissonLock.spEL();
        if (ArrayUtils.isNotEmpty(spEL)) {StandardEvaluationContext context = new StandardEvaluationContext();

            // 上下文对象中的变量
            Map<String, Object> rootObject = getVariables(joinPoint);
            context.setVariables(rootObject);

            SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
            for (String el : spEL) {Expression expression = spelExpressionParser.parseExpression(el);
                String value = expression.getValue(context, String.class);
                joiner.add(value);
            }
        }

        // key 生成器
        String keyGenerator = redissonLock.keyGenerator();
        if (StringUtils.isNotEmpty(keyGenerator)) {if (applicationContext.containsBean(keyGenerator)) {RedisKeyGenerator bean = applicationContext.getBean(RedisKeyGenerator.class, keyGenerator);
                MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
                String value = bean.generate(joinPoint.getTarget(), methodSignature.getMethod(), joinPoint.getArgs());
                joiner.add(value);
            }
        }

        redisKey = joiner.toString();

        if (StringUtils.isEmpty(redisKey)) {// MSG_1000008(1000008, "锁 key 不能为空")
            throw new ServiceException(ErrCode.MSG_1000008);
        }

        // 尝试获取锁
        RLock rLock = redissonClient.getLock(redisKey);

        // 对立过期工夫和等待时间单位为纳秒
        long expireTime = redissonLock.expireTime();
        long waitTime = redissonLock.waitTime();
        if (expireTime != -1) {expireTime = redissonLock.expireTimeUnit().toNanos(expireTime);
        }
        if (waitTime != -1) {waitTime = redissonLock.waitTimeUnit().toNanos(waitTime);
        }

        boolean tryLock = rLock.tryLock(waitTime, expireTime, TimeUnit.NANOSECONDS);
        if (!tryLock) {log.info("key=[{}] get the lock failure", redisKey);
            throw ServiceException.getInstance(redissonLock.errorCode(), redisKey);
        }
        log.info("key=[{}] get the lock success", redisKey);

        Object proceed;
        try {proceed = joinPoint.proceed();

        } finally {if (rLock.isHeldByCurrentThread()) {rLock.unlock();
                log.info("key=[{}] release the lock success", redisKey);
            }
        }

        return proceed;
    }

    private static Map<String, Object> getVariables(ProceedingJoinPoint joinPoint) {
        // 切面办法参数
        Object[] args = joinPoint.getArgs();
        // 拿到办法签名
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Map<String, Object> map = new HashMap<>();
        // 把参数名和参数对象仍入汇合
        for (int i = 0; i < methodSignature.getParameterNames().length; i++) {String parameterName = methodSignature.getParameterNames()[i];
            Object arg = args[i];
            map.put(parameterName, arg);
        }
        return map;
    }

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}
}

OGNL 表达式应用参考:

所有申请参数会用参数名和参数值存入一个 Map 中,并设置为根对象。

@RedissonLock(expressions = {
                "req.activityId", 
                "addReq.dayBound.countBound", 
                "list1[0].activityStartTime",
                "list2[0]", 
                "name", 
                "map.v555", 
                "arr[0].activityId"})
public void lockTest(ActivityTerminatedRequest req, 
                     ActivityAddAndUpdateRequest addReq, 
                     List<ActivityAddAndUpdateRequest> list1,
                     List<Integer> list2, String name, 
                     Map<String, String> map, 
                     ActivityAddAndUpdateRequest[] arr) {}

SPEL 表达式应用参考:

所有申请参数会被设置成上下文变量。

@RedissonLock(
        spEL = {"#req.activityId", 
                "#addReq.dayBound.countBound", 
                "#list1[0].activityStartTime",
                "#list2[0]", 
                "#name", 
                "#map['v555']", 
                "#arr[0].activityId"})

public void lockTest(ActivityTerminatedRequest req, 
                     ActivityAddAndUpdateRequest addReq, 
                     List<ActivityAddAndUpdateRequest> list1,
                     List<Integer> list2, String name, 
                     Map<String, String> map, 
                     ActivityAddAndUpdateRequest[] arr) {}

残缺应用参考:

@RedissonLock(waitTime = 10, 
        values = {PromoDomain.PROMO_DOMAIN_NAME, PromoDomain.ACTIVITY_DOMAIN_NAME},
        expressions = {"req.activityId", "addReq.dayBound.countBound", 
                "list1[0].activityStartTime",
                "list2[0]", "name", "map.v555", "arr[0].activityId"},
        spEL = {"#req.activityId", "#addReq.dayBound.countBound",
                 "#list1[0].activityStartTime",
                "#list2[0]", "#name", "#map['v555']", "#arr[0].activityId"},
        keyGenerator = "activityKeyGenerator")
@Override
public void lockTest(ActivityTerminatedRequest req, 
                        ActivityAddAndUpdateRequest addReq, 
                        List<ActivityAddAndUpdateRequest> list1,
                        List<Integer> list2, 
                        String name, 
                        Map<String, String> map, 
                        ActivityAddAndUpdateRequest[] arr) {


}


@Service(value = "activityKeyGenerator")
public class ActivityKeyGenerator implements RedisKeyGenerator {
    @Override
    public String generate(Object target, Method method, Object... params) {String name = (String)params[4];

        return name + "#Generate";
    }
}

此锁只合适局部场景,红锁,点锁不提供反对。锁重入时,请留神 key 的 ” 一致性 ”。
Note:key 的定义必须以畛域名称和聚合名称打头,避免 key 反复。

援用:

https://commons.apache.org/proper/commons-ognl/language-guide…
https://docs.spring.io/spring-framework/docs/3.2.x/spring-fra…

退出移动版