需求
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); } }