目录
1.2 构造标准评估上下文对象StandardEvaluationContext
1.3 利用标准评估上下文对象StandardEvaluationContext解析EL表达式语法树
3.应用EL表达式的条件注解@OnExpressionCondition
1.EL表达式处理过程
首先看一下EL表达式在代码中的使用方式:
BeanExpressionContext expressionContext = new BeanExpressionContext(beanFactory, null);
Object result = new StandardBeanExpressionResolver().evaluate(expression, expressionContext);
BeanExpressionContext:表示bean表达式上下文对象,用于获取bean对象,这里表示从beanFactory工厂中获取指定bean名称的bean对象;
StandardBeanExpressionResolver:表示标准的bean表达式解析器,实现接口BeanExpressionResolver;对EL表达式字符串,在指定的bean表达式上下文中进行解析;
下面主要看一下BeanExpressionResolver的接口方法evaluate在StandardBeanExpressionResolver的实现逻辑:
/**
* Evaluate the given value as an expression, if applicable;
* return the value as-is otherwise.
* @param value the value to check
* @param evalContext the evaluation context
* @return the resolved value (potentially the given value as-is)
* @throws BeansException if evaluation failed
*/
@Override
@Nullable
public Object evaluate(@Nullable String value, BeanExpressionContext evalContext) throws BeansException {
if (!StringUtils.hasLength(value)) {
return value;
}
try {
Expression expr = this.expressionCache.get(value);
if (expr == null) {
expr = this.expressionParser.parseExpression(value, this.beanExpressionParserContext);
this.expressionCache.put(value, expr);
}
StandardEvaluationContext sec = this.evaluationCache.get(evalContext);
if (sec == null) {
sec = new StandardEvaluationContext(evalContext);
sec.addPropertyAccessor(new BeanExpressionContextAccessor());
sec.addPropertyAccessor(new BeanFactoryAccessor());
sec.addPropertyAccessor(new MapAccessor());
sec.addPropertyAccessor(new EnvironmentAccessor());
sec.setBeanResolver(new BeanFactoryResolver(evalContext.getBeanFactory()));
sec.setTypeLocator(new StandardTypeLocator(evalContext.getBeanFactory().getBeanClassLoader()));
ConversionService conversionService = evalContext.getBeanFactory().getConversionService();
if (conversionService != null) {
sec.setTypeConverter(new StandardTypeConverter(conversionService));
}
customizeEvaluationContext(sec);
this.evaluationCache.put(evalContext, sec);
}
return expr.getValue(sec);
}
catch (Throwable ex) {
throw new BeanExpressionException("Expression parsing failed", ex);
}
}
这里主要包含以下几步:
1.1 根据EL表达式字符串构建抽象语法树(ast)
1.2 构造标准评估上下文对象StandardEvaluationContext
1.3 利用标准评估上下文对象StandardEvaluationContext解析EL表达式语法树,得出计算结果
下面分别进行说明:
1.1 根据EL表达式字符串构建抽象语法树(ast)
构建抽象语法树的关键代码如下:InternalSpelExpressionParser
@Override
protected SpelExpression doParseExpression(String expressionString, ParserContext context) throws ParseException {
try {
this.expressionString = expressionString;
Tokenizer tokenizer = new Tokenizer(expressionString);
tokenizer.process();
this.tokenStream = tokenizer.getTokens();
this.tokenStreamLength = this.tokenStream.size();
this.tokenStreamPointer = 0;
this.constructedNodes.clear();
SpelNodeImpl ast = eatExpression();
if (moreTokens()) {
throw new SpelParseException(peekToken().startPos, SpelMessage.MORE_INPUT, toString(nextToken()));
}
Assert.isTrue(this.constructedNodes.isEmpty());
return new SpelExpression(expressionString, ast, this.configuration);
}
catch (InternalParseException ex) {
throw ex.getCause();
}
}
这里也主要分为2步:
1.根据字符串表达式识别出token流,简单来说就是一个分词的过程;
2.根据构建的token流构建抽象语法树,这里按照EL表达式支持的运算符的优先级进行解析,构建了一个树形结构,每个节点都表示一个运算单元(操作符和若干操作数),比如OpPlus(加法运算),OpMinus(减法运算);
最终的构造的语法数的根节点表示整个表达式的优先级最低的运算单元,比如:1+(2-3),根节点即是:OpPlus,加法运算,两个操作数为1 和(2-3);
1.2 构造标准评估上下文对象StandardEvaluationContext
StandardEvaluationContext对象主要包含了一下几部分功能:
-
beanFactory(用于获取指定bean)
-
environment用于获取指定属性
-
map用于获取map结构的值
-
...
-
并且提供了可扩展的接口方法customizeEvaluationContext
1.3 利用标准评估上下文对象StandardEvaluationContext解析EL表达式语法树
这里利用标准评估上下文对象StandardEvaluationContext来对抽象语法树进行解析,实际是一个深度优先搜索的计算过程,最终返回整个表达式的计算结果;
@Override
@Nullable
public Object getValue(EvaluationContext context) throws EvaluationException {
Assert.notNull(context, "EvaluationContext is required");
ExpressionState expressionState = new ExpressionState(context, this.configuration);
Object result = this.ast.getValue(expressionState);
checkCompile(expressionState);
return result;
}
2.Spring框架中如何使用到EL表达式
Spring在启动时,会配置好SpEl表达式引擎,具体过程是在AbstractApplicationContext的refresh方法当中会对beanFactory进行配置,如下:
/**
* Configure the factory's standard context characteristics,
* such as the context's ClassLoader and post-processors.
* @param beanFactory the BeanFactory to configure
*/
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// Tell the internal bean factory to use the context's class loader etc.
beanFactory.setBeanClassLoader(getClassLoader());
beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));
// Configure the bean factory with context callbacks.
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
// BeanFactory interface not registered as resolvable type in a plain factory.
// MessageSource registered (and found for autowiring) as a bean.
beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
beanFactory.registerResolvableDependency(ResourceLoader.class, this);
beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
beanFactory.registerResolvableDependency(ApplicationContext.class, this);
// Detect a LoadTimeWeaver and prepare for weaving, if found.
if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
// Set a temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
// Register default environment beans.
if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
}
if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
}
if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
}
}
3.应用EL表达式的条件注解@OnExpressionCondition
@OnExpressionCondition 是Spring提供的根据表达式计算结果来有条件加载bean的一种方式;
/**
* Configuration annotation for a conditional element that depends on the value of a SpEL
* expression.
*
* @author Dave Syer
* @since 1.0.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnExpressionCondition.class)
public @interface ConditionalOnExpression {
/**
* The SpEL expression to evaluate. Expression should return {@code true} if the
* condition passes or {@code false} if it fails.
* @return the SpEL expression
*/
String value() default "true";
}
主要实现代码如下,包括占位符解析,SpEL表达式计算;
/**
* A Condition that evaluates a SpEL expression.
*
* @author Dave Syer
* @author Stephane Nicoll
* @see ConditionalOnExpression
*/
@Order(Ordered.LOWEST_PRECEDENCE - 20)
class OnExpressionCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
String expression = (String) metadata.getAnnotationAttributes(ConditionalOnExpression.class.getName())
.get("value");
expression = wrapIfNecessary(expression);
ConditionMessage.Builder messageBuilder = ConditionMessage.forCondition(ConditionalOnExpression.class,
"(" + expression + ")");
expression = context.getEnvironment().resolvePlaceholders(expression);
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
if (beanFactory != null) {
boolean result = evaluateExpression(beanFactory, expression);
return new ConditionOutcome(result, messageBuilder.resultedIn(result));
}
return ConditionOutcome.noMatch(messageBuilder.because("no BeanFactory available."));
}
private Boolean evaluateExpression(ConfigurableListableBeanFactory beanFactory, String expression) {
BeanExpressionResolver resolver = beanFactory.getBeanExpressionResolver();
if (resolver == null) {
resolver = new StandardBeanExpressionResolver();
}
BeanExpressionContext expressionContext = new BeanExpressionContext(beanFactory, null);
Object result = resolver.evaluate(expression, expressionContext);
return (result != null && (boolean) result);
}
/**
* Allow user to provide bare expression with no '#{}' wrapper.
* @param expression source expression
* @return wrapped expression
*/
private String wrapIfNecessary(String expression) {
if (!expression.startsWith("#{")) {
return "#{" + expression + "}";
}
return expression;
}
}