前言
上一篇文章中提到过,在处理字符串类型的属性值时,会调用 BeanDefinitionValueResolver 类的 doEvaluate 方法,对字符串再做一次解析,到底在解析什么呢?
其实这个方法主要是为了处理 SpEL 表达式。
1 SpEL表达式的3种用法
首先,我们一起看看SpEL表达式的3种用法是怎么玩的。
1.1 在XML中配置
在 Spring 的 XML 文件中允许配置下面这样的属性值:
<bean name="test" class="cn.susan.Test">
<property name="name" value="#{test.name}" />
</bean>即在property的value中,可以使用#{}配置SpEL表达式。
这里的value不是固定值,可以根据test.name的配置,动态的变化。
doEvaluate 方法就是为了将#{test.name},转换成真正的值,比如:susan。
1.2 使用@Value注解
目前最常用SpEL的用法的应该是@Value注解的用法了:
@RestController
public class TestController {
@Value("#{susan.userId}")
private String userId;
}可以直接在@Value中,通过SpEL表达式设置配置参数。
1.3 硬编码
还可以通过硬编码的方式使用SpEL表达式,例如:
@GetMapping("testSpEL")
public String testSpEL() {
//创建ExpressionParser解析表达式
ExpressionParser parser = new SpelExpressionParser();
SpelExpression exp = (SpelExpression)parser.parseExpression("#user.id");
int int1 = parser.parseExpression("3+2").getValue(Integer.class);//5
int int2 = parser.parseExpression("3*2").getValue(Integer.class);//6
//创建一个虚拟的容器EvaluationContext
StandardEvaluationContext ctx = new StandardEvaluationContext();
//向容器内添加bean
User user = new User();
user.setId("123");
ctx.setVariable("user", user);
//得到user对象的id属性值
String value = (String)exp.getValue(ctx);
return value;
}需要创建一个ExpressionParser解析表达式,然后获取SpelExpression,通过这个对象解析EL表达式。
这3种方式的底层都是通过doEvaluate方法解析SpEL表达式的,接下来,我们的重点是看看Spring是怎么做的。
2 doEvaluate 方法
doEvaluate方法很简单,就一行代码。
它会调用 beanFactory 的 evaluateBeanDefinitionString 方法去做具体的代码逻辑处理。
在 AbstractBeanFactory 类中可以找到这个方法:
其中beanExpressionResolver其实是AbstractBeanFactory类的成员变量:
这个类提供了beanExpressionResolver的getter和setter方法。
其实beanExpressionResolver,是在AbstractBeanFactory类的prepareBeanFactory方法中设置进去的:
在refresh方法中会调用的prepareBeanFactory方法,我们之前看到过:
EL表达式的功能受到开关:spring.spel.ignore 的控制,当它为true时,则表示Spring不支持SpEL表达功能,默认是false。
也就是说,我们可以通过spring.spel.ignore参数,关闭SpEL表达式的功能。
3 evaluate方法
上面evaluateBeanDefinitionString方法最关键的代码是调用了BeanExpressionResolver类的evaluate方法,我们一起看看这个方法做了些什么。
其中Expression是一个接口,这个接口中定义了属性值的获取方法:
目前主要有下面三个实现类:
4 parseExpression方法
其中有一般非常关键,Spring需要对属性值中的表达式进行解析,这个功能是通过TemplateAwareExpressionParser类的parseExpression方法实现的。
最终会调用InternalSpelExpressionParser类的doParseExpression方法做解析。
其中最关键的是词法分析和语法分析。
词法分析是通过Tokenizer类的process方法实现的:
它会把属性值一个字符一个字符遍历,将满足条件的字符,转换成对应的Token对象,保存到一个集合中。
语法分析是通过eatExpression方法实现的:
它会分析属性值中的语法,生成一个ast树。这个代码非常复杂,我在这里就不展开了,感兴趣的小伙伴后面可以研究一下。
5 getValue方法
对SpEL表达式的处理,关键的逻辑还是在expr的getValue方法中,我们一起看看它是怎么玩的。
最关键的一步是从ast树中获取属性值:
ast树是SpelNodeImpl类型的,它会调用到getValueInternal方法,而这个方法是抽象方法:
具体的业务逻辑在它的子类中实现的,下面都是SpelNodeImpl的子类:
其中在Indexer和PropertyOrFieldReference这两个子类中,用到了之前添加的PropertyAccessor属性访问器。
6 工作原理
SpEL表达式的处理过程涉及到的代码量还是挺多的,很多细节没有展开,不然大家非常容易看晕,下面用一张图总结一下SpEL表达式的工作原理。

主要流程如下:
- 读取属性值。
- 创建InteralSpelExceptionParser表达式解析器。
- 使用表达式解析器进行词法分析。
- 使用表达式解析器进行语法分析。
- 生成Expression表达式对象。
- 创建ExpressionContext对象。
- 使用Expression表达式对象,从ExpressionContext上下文中获取值。