Spring 缓存注解 SpEL 表达式解析

Posted 竺旭东

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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();
    }
}

以上是关于Spring 缓存注解 SpEL 表达式解析的主要内容,如果未能解决你的问题,请参考以下文章

Spring 使用介绍—— SpEL

Spring 系列篇之表达式语言(SpEL)

Spring专题「实战系列」针对Spring注解@ConditionalOnExpression详细使用说明

Spring专题「实战系列」针对Spring注解@ConditionalOnExpression详细使用说明

spring中的@Value注解

#私藏项目实操分享#Spring专题「实战系列」spring注解@ConditionalOnExpression详细使用说明