原文:https://cloud.tencent.com/developer/article/1497676
前言
Spring Expression Language
(简称 SpEL)是一个支持查询和操作运行时对象导航图功能
的强大的表达式语言。它的语法类似于传统 EL,但提供额外的功能,最出色的就是函数调用和简单字符串的模板函数。
这不得不介绍的SpEL的概念。Sp:Spring,EL:Expression Language
。我们熟悉的还有比如JSP中的EL表达式、Struts中的OGNL
等等。那那那既然有了它们,为何还要SpEL呢?
SpEL 创建的初衷是给 Spring 社区提供一种简单而高效的表达式语言,一种可贯穿整个 Spring 产品组的语言。这种语言的特性基于 Spring 产品的需求而设计,这是它出现的一大特色。
在我们离不开Spring框架的同时,其实我们也已经离不开SpEL了,因为它太好用、太强大了。此处我贴出官网的这张图:

从图中可以看出SpEL
的重要,它在Spring家族中如同基石一般的存在。 SpEL是spring-expression
这个jar提供给我们的功能,它从Spring3.x版本开始提供~
备注:
SpEL
并不依附于Spring
容器,它也可以独立于容器解析。因此,我们在书写自己的逻辑、框架的时候,也可以借助SpEL
定义支持一些高级表达式~ 需注意一点若看到这么用:#{ systemProperties['user.dir'] }
,我们知道systemProperties
是Spring容器就内置的,至于为何?之前在分析容器原理的的时候有介绍过~ 还有systemEnvironment
等等等等都是可以直接使用的~
关于systemProperties
和systemEnvironment
具体取值可参考:【小家Java】Java环境变量(Env)和系统属性(Property)详解—工具文章
阅读前准备
需要说明是:本文着眼于SpEL原理、源码层面的剖析,因此阅读本文之前,我默认小伙伴已经是掌握和可以熟练使用SpEL的的,这里贴出两个文档型兄弟博文供以参考: Spring学习总结(四)——表达式语言 Spring Expression Language Spring Expression Language(SpEL) 4 学习笔记
SpEL的使用基本总结如下:
- SpEL 字面量: - 整数:#{8} - 小数:#{8.8} - 科学计数法:#{1e4} - String:可以使用单引号或者双引号作为字符串的定界符号。 - Boolean:#{true}
- SpEL引用bean , 属性和方法: - 引用其他对象:#{car} - 引用其他对象的属性:#{car.brand} - 调用其它方法 , 还可以链式操作:#{car.toString()} - 调用静态方法静态属性:#{T(java.lang.Math).PI}
- SpEL支持的运算符号: - 算术运算符:+,-,*,/,%,^(加号还可以用作字符串连接) - 比较运算符:< , > , == , >= , <= , lt , gt , eg , le , ge - 逻辑运算符:and , or , not , | - if-else 运算符(类似三目运算符):?:(temary),
?:(Elvis)
- 正则表达式:#{admin.email matches ‘[a-zA-Z0-9._%±]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}’}
基本原理
为了更好的叙述,以一个简单例子作为参照:
public static void main(String[] args) {
String expressionStr = "1 + 2";
ExpressionParser parpser = new SpelExpressionParser(); //SpelExpressionParser是Spring内部对ExpressionParser的唯一最终实现类
Expression exp = parpser.parseExpression(expressionStr); //把该表达式,解析成一个Expression对象:SpelExpression
// 方式一:直接计算
Object value = exp.getValue();
System.out.println(value.toString()); //3
// 若你在@Value中或者xml使用此表达式,请使用#{}包裹~~~~~~~~~~~~~~~~~
System.out.println(parpser.parseExpression("T(System).getProperty('user.dir')").getValue()); //E:\work\remotegitcheckoutproject\myprojects\java\demo-war
System.out.println(parpser.parseExpression("T(java.lang.Math).random() * 100.0").getValue()); //27.38227555400853
// 方式二:定义环境变量,在环境内计算拿值
// 环境变量可设置多个值:比如BeanFactoryResolver、PropertyAccessor、TypeLocator等~~~
// 有环境变量,就有能力处理里面的占位符 ${}
EvaluationContext context = new StandardEvaluationContext();
System.out.println(exp.getValue(context)); //3
}
任何语言都需要有自己的语法,SpEL当然也不例外。所以我们应该能够想到,给一个字符串最终解析成一个值,这中间至少得经历: 字符串 -> 语法分析 -> 生成表达式对象 -> (添加执行上下文) -> 执行此表达式对象 -> 返回结果
关于SpEL
的几个概念:
- 表达式(“干什么”):
SpEL
的核心,所以表达式语言都是围绕表达式进行的 - 解析器(“谁来干”):用于将字符串表达式解析为表达式对象
- 上下文(“在哪干”):表达式对象执行的环境,该环境可能定义变量、定义自定义函数、提供类型转换等等
- root根对象及活动上下文对象(“对谁干”):root根对象是默认的活动上下文对象,活动上下文对象表示了当前表达式操作的对象
这是对于解析一个语言表达式比较基本的一个处理步骤,为了更形象的表达出意思,绘制一幅图友好展示如下:

步骤解释:
- 按照SpEL支持的语法结构,写出一个
expressionStr
- 准备一个表达式解析器
ExpressionParser
,调用方法parseExpression()
对它进行解析。这一步至少完成了如下三件事:- 使用一个专门的断词器
Tokenizer
,将给定的表达式字符串拆分为Spring可以认可的数据格式 - 根据断词器处理的操作结果生成相应的语法结构
- 在这处理过程之中就需要进行表达式的对错检查(语法格式不对要精准报错出来)
- 使用一个专门的断词器
- 将已经处理好后的表达式定义到一个专门的对象
Expression
里,等待结果 - 由于表达式内可能存在占位符变量
${}
,所以还不太适合马上直接getValue()
(若不需要解析占位符那就直接getValue()也是可以拿到值的)。所以在计算之前还得设置一个表达式上下文对象`EvaluationContext`(这一步步不是必须的) - 替换好占位符内容后,利用表达式对象计算出最终的结果~~~~
相信从这个Demo可以了解到SpEL处理的一个过程逻辑,有处理流程有一个整体的认识了。那么接下来就是要拆分到各个核心组件的内部,一探究竟~
ExpressionParser:表达式解析器
将表达式字符串解析为可计算的已编译表达式。支持分析模板(Template)
和标准表达式字符串
。 它是一个抽象,并没有要求具体的语法规则,Spring实现的语法规则是:SpEL语法。
// @since 3.0
public interface ExpressionParser {
// 他俩都是把字符串解析成一个Expression对象~~~~ 备注expressionString都是可以被repeated evaluation的
Expression parseExpression(String expressionString) throws ParseException;
Expression parseExpression(String expressionString, ParserContext context) throws ParseException;
}
此处,ParserContext
:提供给表达式分析器的输入,它可能影响表达式分析/编译例程。它会对我们解析表达式字符串的行为影响
ParserContext
public interface ParserContext {
// 是否是模版表达式。 比如:#{3 + 4}
boolean isTemplate();
// 模版的前缀、后缀 子类是可以定制化的~~~
String getExpressionPrefix();
String getExpressionSuffix();
// 默认提供的实例支持:#{} 的形式 显然我们可以改变它但我们一般并不需要这么去做~
ParserContext TEMPLATE_EXPRESSION = new ParserContext() {
@Override
public boolean isTemplate() {
return true;
}
@Override
public String getExpressionPrefix() {
return "#{";
}
@Override
public String getExpressionSuffix() {
return "}";
}
};
}
它只有一个实现类:TemplateParserContext
。(ParserContext.TEMPLATE_EXPRESSION也是该接口的一个内部实现,我们可以直接引用)
关于
StandardBeanExpressionResolver
的内部类实现,也是一个非常基础的实现。关于@Value的原理文章有提到
public class TemplateParserContext implements ParserContext {
private final String expressionPrefix;
private final String expressionSuffix;
// 默认就是它了~~~
public TemplateParserContext() {
this("#{", "}");
}
@Override
public final boolean isTemplate() {
return true;
}
@Override
public final String getExpressionPrefix() {
return this.expressionPrefix;
}
@Override
public final String getExpressionSuffix() {
return this.expressionSuffix;
}
}
~ExpressionParser
的继承树如下

TemplateAwareExpressionParser
它是一个支持解析模版Template的解析器。
// @since 3.0 它是一个抽象类
public abstract class TemplateAwareExpressionParser implements ExpressionParser {
@Override
public Expression parseExpression(String expressionString) throws ParseException {
return parseExpression(expressionString, null);
}
@Override
public Expression parseExpression(String expressionString, @Nullable ParserContext context) throws ParseException {
// 若指定了上下文,并且是模版 就走parseTemplate
if (context != null && context.isTemplate()) {
return parseTemplate(expressionString, context);
} else {
// 抽象方法 子类去实现~~~
return doParseExpression(expressionString, context);
}
}
private Expression parseTemplate(String expressionString, ParserContext context) throws ParseException {
// 若解析字符串是空串~~~~~
if (expressionString.isEmpty()) {
return new LiteralExpression("");
}
// 若只有一个模版表达式,直接返回。否则会返回一个CompositeStringExpression,聚合起来的表达式~
Expression[] expressions = parseExpressions(expressionString, context);
if (expressions.length == 1) {
return expressions[0];
} else {
return new CompositeStringExpression(expressionString, expressions);
}
}
// ... parseExpressions的实现逻辑 还是稍显复杂的~ 因为支持的case太多了~~~
}
它的子类实现有:InternalSpelExpressionParser
和SpelExpressionParser
SpelExpressionParser
SpEL parser
。该实例是可重用的和线程安全的(原因?此处卖个关子,小伙伴可自行想想)
public class SpelExpressionParser extends TemplateAwareExpressionParser {
private final SpelParserConfiguration configuration;
public SpelExpressionParser() {
this.configuration = new SpelParserConfiguration();
}
public SpelExpressionParser(SpelParserConfiguration configuration) {
Assert.notNull(configuration, "SpelParserConfiguration must not be null");
this.configuration = configuration;
}
// 最终都是委托给了Spring的内部使用的类:InternalSpelExpressionParser--> 内部的SpEL表达式解析器~~~
public SpelExpression parseRaw(String expressionString) throws ParseException {
return doParseExpression(expressionString, null);
}
// 这里需要注意:因为是new的,所以每次都是一个新对象,所以它是线程安全的~
@Override
protected SpelExpression doParseExpression(String expressionString, @Nullable ParserContext context) throws ParseException {
return new InternalSpelExpressionParser(this.configuration).doParseExpression(expressionString, context);
}
}
这里的SpelParserConfiguration
表示:顾名思义它表示SpEL的配置类。在构建SpelExpressionParser时我们可以给其传递一个SpelParserConfiguration对象以对SpelExpressionParser进行配置。其可以用于指定在遇到List或Array为null时是否自动new一个对应的实例(一般不建议修改此值~以保持语义统一)
// 它是个public类,因为`StandardBeanExpressionResolver`也使用到了它~~~
public class SpelParserConfiguration {
// OFF IMMEDIATE(expressions are compiled as soon as possible) MIXED
private static final SpelCompilerMode defaultCompilerMode;
static {
// 它的值可由`spring.properties`里面的配置改变~~~~ 所以你可以在你的类路径下放置一个文件,通过`spring.expression.compiler.mode=IMMEDIATE`来控制编译行为
String compilerMode = SpringProperties.getProperty("spring.expression.compiler.mode");
defaultCompilerMode = (compilerMode != null ?
SpelCompilerMode.valueOf(compilerMode.toUpperCase()) : SpelCompilerMode.OFF);
}
// 调用者若没指定,会使用上面的默认的~
private final SpelCompilerMode compilerMode;
@Nullable
private final ClassLoader compilerClassLoader;
// 碰到为null的,是否给自动new一个对象,比如new String(),new ArrayList()等等~
private final boolean autoGrowNullReferences;
// 专门针对于集合是否new
private final boolean autoGrowCollections;
// 集合能够自动增长到的最大值~~~~
private final int maximumAutoGrowSize;
// 省略get/set方法~~~后面会给一个自定义配置的示例~~~
}
InternalSpelExpressionParser
上面知道SpelExpressionParser
最终都是委托它里做的,并且configuration也交给它,然后调用doParseExpression
方法处理~
// 它是Spring内部使用的类~
class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
private static final Pattern VALID_QUALIFIED_ID_PATTERN = Pattern.compile("[\\p{L}\\p{N}_$]+");
private final SpelParserConfiguration configuration; //SpEL的配置
// 此处用一个双端队列 来保存表达式的每一个节点,每个节点都是一个SpelNode 该对象记录着位置、子节点、父节点等等~~~
private final Deque<SpelNodeImpl> constructedNodes = new ArrayDeque<>();
private String expressionString = ""; // 带解析的表达式字符串~
// Token流:token保存着符号类型(如int(,]+=?>=等等各种符号 非常之多) 然后记录着它startPos和endPos
private List<Token> tokenStream = Collections.emptyList();
// length of a populated token stream
private int tokenStreamLength;
// Current location in the token stream when processing tokens
private int tokenStreamPointer;
// 唯一的一个构造函数~
public InternalSpelExpressionParser(SpelParserConfiguration configuration) {
this.configuration = configuration;
}
@Override
protected SpelExpression doParseExpression(String expressionString, @Nullable ParserContext context) throws ParseException {
try {
this.expressionString = expressionString;
// Tokenizer就是分词器。把待解析的表达式交给它分词~~~
Tokenizer tokenizer = new Tokenizer(expressionString);
// process处理,得到tokenStream 并且记录上它的总长度 并且标记当前处理点为0
this.tokenStream = tokenizer.process();
this.tokenStreamLength = this.tokenStream.size();
this.tokenStreamPointer = 0;
this.constructedNodes.clear(); // 显然把当前节点清空~~
SpelNodeImpl ast = eatExpression();
Assert.state(ast != null, "No node");
Token t = peekToken();
if (t != null) {
throw new SpelParseException(t.startPos, SpelMessage.MORE_INPUT, toString(nextToken()));
}
Assert.isTrue(this.constructedNodes.isEmpty(), "At least one node expected");
// 最终:每一个SpelNodeImpl 它就是一个SpelExpression表达式,但会出去。\
// 此时:只是把我们的字符串解析成为一个SpelExpression,还没有参与赋值、计算哦~~~~
return new SpelExpression(expressionString, ast, this.configuration);
} catch (InternalParseException ex) {
throw ex.getCause();
}
}
... // 解析表达式的逻辑非常的复杂,Spring团队老牛逼了,竟然支持到了这么多的功能~~~~
}
这么一来,我们的ExpressionParser
就算解释完成了。绝大部分情况下我们最终都是使用了SpelExpressionParser
去解析标准的语言表达式。
但是,但是,但是我们上面也说了,它还支持Template
模式,下面以一个Demo加深了解:
SpEL对Template模式支持
public static void main(String[] args) {
String greetingExp = "Hello, #{#user} ---> #{T(System).getProperty('user.home')}";
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("user", "fsx");
Expression expression = parser.parseExpression(greetingExp, new TemplateParserContext());
System.out.println(expression.getValue(context, String.class)); //Hello, fsx ---> C:\Users\fangshixiang
}
这个功能就有点像加强版的字符串格式化了。它的执行步骤描述如下:
- 创建一个模板表达式,所谓模板就是带字面量和表达式的字符串。其中#{}表示表达式的起止。上面的
#user
是表达式字符串,表示引用一个变量(注意这个写法,有两个#号) - 解析字符串。其实SpEL框架的抽象是与具体实现无关的,只是我们这里使用的都是
SpelExpressionParser
- 通过
evaluationContext.setVariable
可以在上下文中设定变量。 - 使用
Expression.getValue()
获取表达式的值,这里传入了Evalution上下文,第二个参数是类型参数,表示返回值的类型。
只有
Template
模式的时候,才需要#{},不然SpEL就是里面的内容即可,如1+2就是一个SpEL 至于@Value
为何需要#{spel表示是内容}
这样包裹着,是因为它是这样的expr = this.expressionParser.parseExpression(value, this.beanExpressionParserContext);
,也就是说它最终是parseTemplate()
这个去解析的~~~~ 如果parse的时候传的context是null啥的,就不会解析外层#{}了
Expression
表示的是表达式对象。能够根据上下文对象对自身进行计算的表达式。
封装以前分析的表达式字符串的详细信息。
// @since 3.0 表达式计算的通用抽象。 该接口提供的方法非常非常之多~~~ 但不要怕大部分都是重载的~~~
public interface Expression {
// 返回原始表达式的字符串~~~
String getExpressionString();
// 使用一个默认的标准的context执行计算~~~
@Nullable
Object getValue() throws EvaluationException;
// SpEL内部帮你转换~~~ 使用的是默认的context
@Nullable
<T> T getValue(@Nullable Class<T> desiredResultType) throws EvaluationException;
// 根据指定的根对象计算此表达式
@Nullable
Object getValue(Object rootObject) throws EvaluationException;
@Nullable
<T> T getValue(Object rootObject, @Nullable Class<T> desiredResultType) throws EvaluationException;
// 根据指定的上下文:EvaluationContext来计算值~~~ rootObject:跟对象
@Nullable
Object getValue(EvaluationContext context) throws EvaluationException;
// 以rootObject作为表达式的root对象来计算表达式的值。
// root对象:比如parser.parseExpression("name").getValue(person);相当于去person里拿到name属性的值。这个person就叫root对象
@Nullable
Object getValue(EvaluationContext context, Object rootObject) throws EvaluationException;
@Nullable
<T> T getValue(EvaluationContext context, @Nullable Class<T> desiredResultType) throws EvaluationException;
@Nullable
<T> T getValue(EvaluationContext context, Object rootObject, @Nullable Class<T> desiredResultType)
throws EvaluationException;
// 返回可传递给@link setvalue的最一般类型
@Nullable
Class<?> getValueType() throws EvaluationException;
@Nullable
Class<?> getValueType(Object rootObject) throws EvaluationException;
@Nullable
Class<?> getValueType(EvaluationContext context) throws EvaluationException;
@Nullable
Class<?> getValueType(EvaluationContext context, Object rootObject) throws EvaluationException;
@Nullable
TypeDescriptor getValueTypeDescriptor() throws EvaluationException;
@Nullable
TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException;
@Nullable
TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException;
@Nullable
TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject) throws EvaluationException;
// 确定是否可以写入表达式,即可以调用setValue()
boolean isWritable(Object rootObject) throws EvaluationException;
boolean isWritable(EvaluationContext context) throws EvaluationException;
boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException;
// 在提供的上下文中将此表达式设置为提供的值。
void setValue(Object rootObject, @Nullable Object value) throws EvaluationException;
void setValue(EvaluationContext context, @Nullable Object value) throws EvaluationException;
void setValue(EvaluationContext context, Object rootObject, @Nullable Object value) throws EvaluationException;
}
它的继承树如下:

SpelExpression
这个是我们核心,甚至也是目前SpEL的唯一实现。 表达式可以独立计算,也可以在指定的上下文中计算。
在表达式计算期间,可能会要求上下文解析:对类型、bean、属性和方法的引用。
public class SpelExpression implements Expression {
// 在编译表达式之前解释该表达式的次数。
private static final int INTERPRETED_COUNT_THRESHOLD = 100;
// 放弃前尝试编译表达式的次数
private static final int FAILED_ATTEMPTS_THRESHOLD = 100;
private final String expression;
// AST:抽象语法树~
private final SpelNodeImpl ast; // SpelNodeImpl的实现类非常非常之多~~~
private final SpelParserConfiguration configuration;
@Nullable
private EvaluationContext evaluationContext; // 如果没有指定,就会用默认的上下文 new StandardEvaluationContext()
// 如果该表达式已经被编译了,就会放在这里 @since 4.1 Spring内部并没有它的实现类 尴尬~~~编译是要交给我们自己实现???
@Nullable
private CompiledExpression compiledAst;
// 表达式被解释的次数-达到某个限制时可以触发编译
private volatile int interpretedCount = 0;
// 编译尝试和失败的次数——使我们最终放弃了在似乎不可能编译时尝试编译它的尝试。
private volatile int failedAttempts = 0;
// 唯一构造函数~
public SpelExpression(String expression, SpelNodeImpl ast, SpelParserConfiguration configuration) {
this.expression = expression;
this.ast = ast;
this.configuration = configuration;
}
...
// 若没有指定,这里会使用默认的StandardEvaluationContext上下文~
public EvaluationContext getEvaluationContext() {
if (this.evaluationContext == null) {
this.evaluationContext = new StandardEvaluationContext();
}
return this.evaluationContext;
}
...
@Override
@Nullable
public Object getValue() throws EvaluationException {
// 如果已经被编译过,就直接从编译后的里getValue即可~~~~
if (this.compiledAst != null) {
try {
EvaluationContext context = getEvaluationContext();
return this.compiledAst.getValue(context.getRootObject().getValue(), context);
} catch (Throwable ex) {
// If running in mixed mode, revert to interpreted
if (this.configuration.getCompilerMode() == SpelCompilerMode.MIXED) {
this.interpretedCount = 0;
this.compiledAst = null;
} else {
throw new SpelEvaluationException(ex, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION);
}
}
}
ExpressionState expressionState = new ExpressionState(getEvaluationContext(), this.configuration);
// 比如此处SeEl是加法+,所以ast为:OpPlus语法树去处理的~~~
Object result = this.ast.getValue(expressionState);
// 检查是否需要编译它~~~
checkCompile(expressionState);
return result;
}
... // 备注:数据转换都是EvaluationContext.getTypeConverter() 来进行转换
// 注意:此处的TypeConverter为`org.springframework.expression`的 只有一个实现类:StandardTypeConverter
// 它内部都是委托给ConversionService去做的,具体是`DefaultConversionService`去做的~
@Override
@Nullable
public Class<?> getValueType() throws EvaluationException {
return getValueType(getEvaluationContext());
}
@Override
@Nullable
public Class<?> getValueType(Object rootObject) throws EvaluationException {
return getValueType(getEvaluationContext(), rootObject);
}
@Override
@Nullable
public Class<?> getValueType(EvaluationContext context, Object rootObject) throws EvaluationException