基於自定義註解和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);
    }


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