Spring 緩存註解 SpEL 表達式解析

緩存註解上 key、condition、unless 等 SpEL 表達式的解析

SpEl 支持的計算變量:
1)#ai、#pi、#命名參數【i 表示參數下標,從 0 開始】
2)#result:CachePut 操作和後處理 CacheEvict 操作都可使用
3)#root:CacheExpressionRootObject 對象

計算上下文根對象

/**
 *  緩存註解 SpEL 表達式計算上下文根對象
 */
class CacheExpressionRootObject {
    /**
     *  有效的緩存集合
     */
    private final Collection<? extends Cache> caches;
    /**
     *  目標方法
     */
    private final Method method;
    /**
     *  方法參數 
     */
    private final Object[] args;
    /**
     *  目標對象 
     */
    private final Object target;
    /**
     *  目標對象 Class 類型
     */
    private final Class<?> targetClass;
}

緩存計算上下文【附加方法參數和返回結果作爲計算變量】

/**
 *  基於方法的表達式計算上下文
 * @since 4.2
 */
public class MethodBasedEvaluationContext extends StandardEvaluationContext {
    /**
     *  目標方法 
     */
    private final Method method;
    /**
     *  參數數組 
     */
    private final Object[] arguments;
    /**
     *  參數名發現者
     */
    private final ParameterNameDiscoverer parameterNameDiscoverer;
    /**
     *  參數變量是否已經加載
     */
    private boolean argumentsLoaded = false;


    public MethodBasedEvaluationContext(Object rootObject, Method method, Object[] arguments,
            ParameterNameDiscoverer parameterNameDiscoverer) {
        super(rootObject);
        this.method = method;
        this.arguments = arguments;
        this.parameterNameDiscoverer = parameterNameDiscoverer;
    }

    @Override
    @Nullable
    public Object lookupVariable(String name) {
        // 1)嘗試從變量映射中讀取指定名稱的對象
        Object variable = super.lookupVariable(name);
        if (variable != null) {
            return variable;
        }
        // 2)未讀到 && 方法參數未加載
        if (!this.argumentsLoaded) {
            // 加載方法參數
            lazyLoadArguments();
            this.argumentsLoaded = true;
            // 再次讀取
            variable = super.lookupVariable(name);
        }
        return variable;
    }

    protected void lazyLoadArguments() {
        // 無參數則直接退出
        if (ObjectUtils.isEmpty(this.arguments)) {
            return;
        }
        
        // 讀取目標方法的所有參數名稱
        String[] paramNames = this.parameterNameDiscoverer.getParameterNames(this.method);
        // 計算形參個數,以解析到的參數名優先【可能存在可變參數】
        int paramCount = (paramNames != null ? paramNames.length : this.method.getParameterCount());
        // 實際傳入的參數個數
        int argsCount = this.arguments.length;

        for (int i = 0; i < paramCount; i++) {
            Object value = null;
            /**
             *  實際傳入的參數個數 > 形參個數 
             *  && 將餘下的所有入參都加入到數組中【目標方法最後一個參數是可變參數】
             */
            if (argsCount > paramCount && i == paramCount - 1) {
                value = Arrays.copyOfRange(this.arguments, i, argsCount);
            }
            // 讀取實際參數
            else if (argsCount > i) {
                // Actual argument found - otherwise left as null
                value = this.arguments[i];
            }
            /**
             *  將 ai、pi、實際參數名稱作爲鍵,加入到計算變量映射中
             *  a0、p0 都表示第一個參數,依次類推
             */
            setVariable("a" + i, value);
            setVariable("p" + i, value);
            if (paramNames != null) {
                setVariable(paramNames[i], value);
            }
        }
    }
}

/**
 *  緩存計算上下文,自動將方法參數添加爲計算變量。
 */
class CacheEvaluationContext extends MethodBasedEvaluationContext {
    /**
     *  不可方法的變量集合
     */
    private final Set<String> unavailableVariables = new HashSet<>(1);
    CacheEvaluationContext(Object rootObject, Method method, Object[] arguments,
            ParameterNameDiscoverer parameterNameDiscoverer) {
        super(rootObject, method, arguments, parameterNameDiscoverer);
    }

    /**
     *  添加不可訪問的變量名稱
     */
    public void addUnavailableVariable(String name) {
        this.unavailableVariables.add(name);
    }

    /**
     *  讀取變量的值
     */
    @Override
    @Nullable
    public Object lookupVariable(String name) {
        if (this.unavailableVariables.contains(name)) {
            throw new VariableNotAvailableException(name);
        }
        return super.lookupVariable(name);
    }
}

緩存註解上 SpEL 表達式計算器

/**
 *  用於計算緩存註解上 SpEL 表達式值的工具類
 */
public abstract class CachedExpressionEvaluator {
    /**
     *  表達式解析器
     */
    private final SpelExpressionParser parser;
    /**
     *  參數名稱發現者
     */
    private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    protected CachedExpressionEvaluator(SpelExpressionParser parser) {
        Assert.notNull(parser, "SpelExpressionParser must not be null");
        this.parser = parser;
    }

    protected CachedExpressionEvaluator() {
        this(new SpelExpressionParser());
    }

    protected SpelExpressionParser getParser() {
        return parser;
    }

    protected ParameterNameDiscoverer getParameterNameDiscoverer() {
        return parameterNameDiscoverer;
    }

    /**
     *  將緩存註解上的 SpEL 表達式字符串解析爲 Expression
     */
    protected Expression getExpression(Map<ExpressionKey, Expression> cache,
            AnnotatedElementKey elementKey, String expression) {
        // 創建緩存鍵
        final ExpressionKey expressionKey = createKey(elementKey, expression);
        // 如果已存在則直接返回
        Expression expr = cache.get(expressionKey);
        if (expr == null) {
            // 使用 SpelExpressionParser 解析表達式字符串
            expr = getParser().parseExpression(expression);
            // 寫入緩存
            cache.put(expressionKey, expr);
        }
        return expr;
    }

    private ExpressionKey createKey(AnnotatedElementKey elementKey, String expression) {
        return new ExpressionKey(elementKey, expression);
    }

    protected static class ExpressionKey implements Comparable<ExpressionKey> {
        /**
         *  註解元素+目標類型 
         */
        private final AnnotatedElementKey element;
        /**
         *  SpEL 表達式字符串
         */
        private final String expression;

        protected ExpressionKey(AnnotatedElementKey element, String expression) {
            Assert.notNull(element, "AnnotatedElementKey must not be null");
            Assert.notNull(expression, "Expression must not be null");
            this.element = element;
            this.expression = expression;
        }

        @Override
        public boolean equals(Object other) {
            if (this == other) {
                return true;
            }
            if (!(other instanceof ExpressionKey)) {
                return false;
            }
            final ExpressionKey otherKey = (ExpressionKey) other;
            return element.equals(otherKey.element) &&
                    ObjectUtils.nullSafeEquals(expression, otherKey.expression);
        }

        @Override
        public int hashCode() {
            return element.hashCode() * 29 + expression.hashCode();
        }

        @Override
        public String toString() {
            return element + " with expression \"" + expression + "\"";
        }

        @Override
        public int compareTo(ExpressionKey other) {
            int result = element.toString().compareTo(other.element.toString());
            if (result == 0) {
                result = expression.compareTo(other.expression);
            }
            return result;
        }
    }
}

/**
 *  處理緩存 SpEL 表達式解析的工具類
 */
class CacheOperationExpressionEvaluator extends CachedExpressionEvaluator {
    /**
     *  指示沒有結果變量
     */
    public static final Object NO_RESULT = new Object();
    /**
     *  結果變量不可使用
     */
    public static final Object RESULT_UNAVAILABLE = new Object();
    /**
     *  計算上下文中,保存結果對象的變量名稱
     */
    public static final String RESULT_VARIABLE = "result";
    /**
     *  key 表達式緩存
     */
    private final Map<ExpressionKey, Expression> keyCache = new ConcurrentHashMap<>(64);
    /**
     *  condition 條件表達式緩存
     */
    private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<>(64);
    /**
     *  unless 條件表達式緩存
     */
    private final Map<ExpressionKey, Expression> unlessCache = new ConcurrentHashMap<>(64);

    public EvaluationContext createEvaluationContext(Collection<? extends Cache> caches,
            Method method, Object[] args, Object target, Class<?> targetClass, Method targetMethod,
            @Nullable Object result, @Nullable BeanFactory beanFactory) {
        // 創建計算上下文的根對象
        final CacheExpressionRootObject rootObject = new CacheExpressionRootObject(
                caches, method, args, target, targetClass);
        // 創建緩存計算上下文
        final CacheEvaluationContext evaluationContext = new CacheEvaluationContext(
                rootObject, targetMethod, args, getParameterNameDiscoverer());
        // 1)方法返回值不可用
        if (result == RESULT_UNAVAILABLE) {
            evaluationContext.addUnavailableVariable(RESULT_VARIABLE);
        }
        // 2)方法返回值可用,則以 result 作爲 key 將其加入到計算變量中
        else if (result != NO_RESULT) {
            evaluationContext.setVariable(RESULT_VARIABLE, result);
        }
        if (beanFactory != null) {
            // 寫入 BeanFactoryResolver
            evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
        }
        return evaluationContext;
    }

    /**
     *  計算鍵的值
     */
    @Nullable
    public Object key(String keyExpression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {
        return getExpression(keyCache, methodKey, keyExpression).getValue(evalContext);
    }

    /**
     *  計算 condition 值
     */
    public boolean condition(String conditionExpression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {
        return Boolean.TRUE.equals(getExpression(conditionCache, methodKey, conditionExpression).getValue(
                evalContext, Boolean.class));
    }

    /**
     *  計算 unless 值
     */
    public boolean unless(String unlessExpression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {
        return Boolean.TRUE.equals(getExpression(unlessCache, methodKey, unlessExpression).getValue(
                evalContext, Boolean.class));
    }

    /**
     * Clear all caches.
     */
    void clear() {
        keyCache.clear();
        conditionCache.clear();
        unlessCache.clear();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章