前言

上一篇文章中提到过,在处理字符串类型的属性值时,会调用 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类的成员变量:image这个类提供了beanExpressionResolver的getter和setter方法。image

其实beanExpressionResolver,是在AbstractBeanFactory类的prepareBeanFactory方法中设置进去的:image在refresh方法中会调用的prepareBeanFactory方法,我们之前看到过:imageEL表达式的功能受到开关:spring.spel.ignore 的控制,当它为true时,则表示Spring不支持SpEL表达功能,默认是false。

也就是说,我们可以通过spring.spel.ignore参数,关闭SpEL表达式的功能。

3 evaluate方法

上面evaluateBeanDefinitionString方法最关键的代码是调用了BeanExpressionResolver类的evaluate方法,我们一起看看这个方法做了些什么。

其中Expression是一个接口,这个接口中定义了属性值的获取方法:image目前主要有下面三个实现类:image

4 parseExpression方法

其中有一般非常关键,Spring需要对属性值中的表达式进行解析,这个功能是通过TemplateAwareExpressionParser类的parseExpression方法实现的。

最终会调用InternalSpelExpressionParser类的doParseExpression方法做解析。

其中最关键的是词法分析和语法分析。

词法分析是通过Tokenizer类的process方法实现的:image它会把属性值一个字符一个字符遍历,将满足条件的字符,转换成对应的Token对象,保存到一个集合中。

语法分析是通过eatExpression方法实现的:image它会分析属性值中的语法,生成一个ast树。这个代码非常复杂,我在这里就不展开了,感兴趣的小伙伴后面可以研究一下。

5 getValue方法

对SpEL表达式的处理,关键的逻辑还是在expr的getValue方法中,我们一起看看它是怎么玩的。

最关键的一步是从ast树中获取属性值:

ast树是SpelNodeImpl类型的,它会调用到getValueInternal方法,而这个方法是抽象方法:image具体的业务逻辑在它的子类中实现的,下面都是SpelNodeImpl的子类:image其中在Indexer和PropertyOrFieldReference这两个子类中,用到了之前添加的PropertyAccessor属性访问器。

6 工作原理

SpEL表达式的处理过程涉及到的代码量还是挺多的,很多细节没有展开,不然大家非常容易看晕,下面用一张图总结一下SpEL表达式的工作原理。

image

主要流程如下:

  1. 读取属性值。
  2. 创建InteralSpelExceptionParser表达式解析器。
  3. 使用表达式解析器进行词法分析。
  4. 使用表达式解析器进行语法分析。
  5. 生成Expression表达式对象。
  6. 创建ExpressionContext对象。
  7. 使用Expression表达式对象,从ExpressionContext上下文中获取值。
最后修改:2026 年 06 月 05 日
如果觉得我的文章对你有用,请随意赞赏