基于自定义注解和SpringEL表达式的分布式锁实现

需求

1、项目中不可避免的需要使用分布式保证幂等。所以一个简单可靠,易用的工具提上日程。

2、演进过程 

  • 最开始使用try finally 块实现。代码臃肿。还要时刻记得释放。
  • 改用回调方式封装锁的获取和释放,但是依然臃肿,需要实现成功和获取锁失败的回调方法。然而获取锁失败几乎都做的一样的事。
  • 使用注解,代价就是使用范围是整个方法。需要自己确认好了使用范围。另外第一版不支持Spring EL。想使用参数值做锁实在太麻烦。
  • 改进注解,使用spring EL引擎。提供强大的数据获取功能。并且对返回值使用调用静态方法和创建新对象十分友好。
  • 我们并没有直接使用spirng EL的所有语法。而是选择包装了一下,因为大家对Spring EL认识参差不齐。

 

demo:

@LockMethod(
        value = {
                @ExtractParam(paramName = "accountInfo", fieldName = "accountId"),
                @ExtractParam(paramName = "order", fieldName = "id"),
                @ExtractParam(paramName = "uid")
        }
        , formatter = "lockTest:%s:%s:%s"
        , failureExpression = "new java.util.ArrayList()")
public Object lockTest(TAccountInfo accountInfo, TPayOrder order, Long uid) {
    return CommonResultEntity.success();
}

代码:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(value = LockMethod.class)
public @interface ExtractParam {

    /**
     * 作为锁的参数名称
     */
    String paramName();

    /**
     * 参数的 属性值。可以为空
     */
    String fieldName() default "";
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LockMethod {

    /**
     * 提取的参数
     */
    ExtractParam[] value();
    /**
     * 自定义
     */
    String formatter() default "";

    /**
     * 失败后返回类型
     */
    Class<?> failureType() default void.class;

    /**
     * 失败返回 表达式
     */
    String failureExpression() default "";

}
@Aspect
@Component
@Slf4j
public class LockInterceptor {

    /**
     * spring 参数名称解析器
     */
    private static final ParameterNameDiscoverer LOCAL_VARIABLE_TABLE_PARAMETER_NAME_DISCOVERER
            = new LocalVariableTableParameterNameDiscoverer();
    /**
     * spring el 表达式解析解
     */
    private static final ExpressionParser SPEL_EXPRESSION_PARSER = new SpelExpressionParser();

    /**
     * Elvis运算符 在一些编程语言中(比如C#、Kotlin等)提供该功能,语法是?:。意义是当某变量不为空的时候使用该变量,当该变量为空的时候使用指定的默认值。
     */
    @Around("@annotation(com.xxx.xxx.support.LockMethod)")
    public Object lock(ProceedingJoinPoint pjp) throws Throwable {
        String methodName = pjp.getSignature().getName();
        Object[] args = pjp.getArgs();
        Class<?> classTarget = pjp.getTarget().getClass();
        Class<?>[] par = ((MethodSignature) pjp.getSignature()).getParameterTypes();
        Method targetMethod = classTarget.getMethod(methodName, par);
        String[] parameterNames = LOCAL_VARIABLE_TABLE_PARAMETER_NAME_DISCOVERER.getParameterNames(targetMethod);
        LockMethod lockMethod = targetMethod.getAnnotation(LockMethod.class);
        final String lockName = parseLockName(args, lockMethod, parameterNames);
        log.info("lockName={} act=LockInterceptor", lockName);
        return doLock(pjp, lockMethod, lockName);
    }

    private Object doLock(ProceedingJoinPoint pjp, LockMethod lockMethod, String lockName) {
        return DistributedLock.acquireLock(MDCUtils.getLogStr(), lockName, new LockCallback<Object>() {
            @Override
            public Object onSuccess(String logStr) {
                try {
                    return pjp.proceed();
                } catch (Throwable throwable) {
                    throw new RuntimeException(throwable);
                }
            }

            @Override
            public Object onFailure(String logStr) {
                String onFailureMethodEL = lockMethod.failureExpression();
                if (StringUtils.isEmpty(onFailureMethodEL)) {
                    return null;
                }
                Class<?> onFailureCallType = lockMethod.failureType();
                if (onFailureCallType == void.class) {
                    return SPEL_EXPRESSION_PARSER.parseExpression(onFailureMethodEL).getValue();
                } else {
                    EvaluationContext context = new StandardEvaluationContext(onFailureCallType);
                    Expression expression = SPEL_EXPRESSION_PARSER.parseExpression(onFailureMethodEL);
                    return expression.getValue(context);
                }
            }
        });
    }

    private String parseLockName(Object[] args, LockMethod lockMethod, String[] parameterNames) {
        ExtractParam[] extractParams = lockMethod.value();
        if (extractParams.length == 0) {
            throw new RuntimeException("not allow no extract param");
        }
        List<String> fieldValues = new ArrayList<>();
        Map<String, Object> paramNameMap = buildParamMap(args, parameterNames);
        for (ExtractParam extractParam : extractParams) {
            String paramName = extractParam.paramName();
            Object paramValue = paramNameMap.get(paramName);
            String springEL = extractParam.fieldName();
            String paramFieldValue = "";
            if (StringUtils.isNotEmpty(springEL)) {
                Expression expression = SPEL_EXPRESSION_PARSER.parseExpression(springEL);
                paramFieldValue = expression.getValue(paramValue).toString();
            } else {
                if (isSimpleType(paramValue.getClass())) {
                    paramFieldValue = String.valueOf(paramValue);
                }
            }
            fieldValues.add(paramFieldValue);
        }
        return String.format(lockMethod.formatter(), fieldValues.toArray());
    }

    /**
     * 构建请求参数map
     * @param args 参数列表
     * @param parameterNames 参数名称列表
     * @return key:参数名称 value:参数值
     */
    private Map<String, Object> buildParamMap(Object[] args, String[] parameterNames) {
        Map<String, Object> paramNameMap = new HashMap<>();
        for (int i = 0; i < parameterNames.length; i++) {
            paramNameMap.put(parameterNames[i], args[i]);
        }
        return paramNameMap;
    }

    /**
     * 基本类型 int, double, float, long, short, boolean, byte, char, void.
     */
    private static boolean isSimpleType(Class<?> clazz) {
        return clazz.isPrimitive()
                || clazz.equals(Long.class)
                || clazz.equals(Integer.class)
                || clazz.equals(String.class)
                || clazz.equals(Double.class)
                || clazz.equals(Short.class)
                || clazz.equals(Byte.class)
                || clazz.equals(Character.class)
                || clazz.equals(Float.class)
                || clazz.equals(Boolean.class);
    }


}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章