SpEL概念
快速入门
关键接口
全面用法
bean定义中使用
Spring 表达式语言(简称“SpEL”)是一种强大的表达式语言,支持在运行时查询和操作对象图。语言语法类似于 Unified EL,但提供了额外的功能,最值得注意的是方法调用和基本的字符串模板功能。
虽然 SpEL 是 Spring 产品组合中表达式评估的基础,但它不直接与 Spring 绑定,可以独立使用。
表达式语言支持以下功能:
通过几个案例快速体验SpEL表达式的使用。
纯字面意义的字符串输出,体验使用的基本步骤。
@Test
public void test_hello() {
// 1 定义解析器
SpelExpressionParser parser = new SpelExpressionParser();
// 2 使用解析器解析表达式
Expression exp = parser.parseExpression("'Hello World'");
// 3 获取解析结果
String value = (String) exp.getValue();
System.out.println(value);
}
// 结果
Hello World
在表达式中调用字符串的普通方法和构造方法。
@Test
public void test_String_method() {
// 1 定义解析器
SpelExpressionParser parser = new SpelExpressionParser();
// 2 使用解析器解析表达式
Expression exp = parser.parseExpression("'Hello World'.concat('!')");
// 3 获取解析结果
String value = (String) exp.getValue();
System.out.println(value);
exp = parser.parseExpression("'Hello World'.bytes");
byte[] bytes = (byte[]) exp.getValue();
exp = parser.parseExpression("'Hello World'.bytes.length");
int length = (Integer) exp.getValue();
System.out.println("length: " + length);
// 调用
exp = parser.parseExpression("new String('hello world').toUpperCase()");
System.out.println("大写: " + exp.getValue());
}
// 结果
Hello World!
length: 11
大写: HELLO WORLD
SpEL 更常见的用法是提供针对特定对象实例(称为根对象)进行评估的表达式字符串。案例演示如何从 Inventor 类的实例中检索名称属性或创建布尔条件。
Inventor相关类定义如下
public class Inventor {
private String name;
private String nationality;
private String[] inventions;
private Date birthdate;
private PlaceOfBirth placeOfBirth;
// 省略其它方法
}
public class PlaceOfBirth {
private String city;
private String country;
// 省略其它方法
}
表达式解析测试
@Test
public void test_over_root() {
// 创建 Inventor 对象
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");
// 1 定义解析器
ExpressionParser parser = new SpelExpressionParser();
// 指定表达式
Expression exp = parser.parseExpression("name");
// 在 tesla对象上解析
String name = (String) exp.getValue(tesla);
System.out.println(name); // Nikola Tesla
exp = parser.parseExpression("name == 'Nikola Tesla'");
// 在 tesla对象上解析并指定返回结果
boolean result = exp.getValue(tesla, Boolean.class);
System.out.println(result); // true
}
上面的案例中SpEL表达式的使用步骤中涉及了几个概念和接口:
1+1!=2
通过下面的简单案例debug分析执行过程。
@Test
public void test_debug(){
SpelExpressionParser parser = new SpelExpressionParser();
SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Boolean value = parser.parseExpression("1+1!=2").getValue(context, Boolean.class);
System.out.println(value);
}
源码debug如下,分2大阶段,建议自行debug一次:
解析阶段:InternalSpelExpressionParser#doParseExpression()
无关源码已经删除
// 用户提供的表达式1+1!=2
private String expressionString = "";
// 分词流
private List tokenStream = Collections.emptyList();
@Override
protected SpelExpression doParseExpression(String expressionString, @Nullable ParserContext context)
throws ParseException {
try {
// 1 读取到用户的表达式 1+1!=2
this.expressionString = expressionString;
// 2.1 定义分词器Tokenizer
Tokenizer tokenizer = new Tokenizer(expressionString);
// 2.2 分词器将字符串拆分为分词流
this.tokenStream = tokenizer.process();
this.tokenStreamLength = this.tokenStream.size();
this.tokenStreamPointer = 0;
this.constructedNodes.clear();
// 3 将分词流解析成抽象语法树 表示为SpelNode接口
SpelNodeImpl ast = eatExpression();
Assert.state(ast != null, "No node");
// 4、将抽象语法树包装成 Expression 表达式对象
return new SpelExpression(expressionString, ast, this.configuration);
}
catch (InternalParseException ex) {
throw ex.getCause();
}
}
评估求值阶段:SpelExpression#getValue()
,无关源码已经删除
// 解析阶段生成的抽象语法树对象 SpelNodeImpl
private final SpelNodeImpl ast;
public T getValue(EvaluationContext context, @Nullable Class expectedResultType) throws EvaluationException {
Assert.notNull(context, "EvaluationContext is required");
// ...
// 6.1 应用活动上下文和解析器的配置
ExpressionState expressionState = new ExpressionState(context, this.configuration);
// 6.2 在上下中抽象语法树进行评估求值
TypedValue typedResultValue = this.ast.getTypedValue(expressionState);
checkCompile(expressionState);
// 6.3 将结果进行类型转换
return ExpressionUtils.convertTypedValue(context, typedResultValue, expectedResultType);
}
方便理解,流程图如下图:
汇总下执行过程:
1+1!=2
ExpressionParser 接口将表达式字符串解析为可以计算的编译表达式。支持解析模板以及标准表达式字符串。
关键方法parseExpressio()
,在解析失败时抛出 ParseException 异常。
public interface ExpressionParser {
// 解析表达式字符串并返回一个可用于重复评估的表达式对象。
Expression parseExpression(String expressionString) throws ParseException;
// 解析表达式字符串并返回一个可用于重复评估的表达式对象。 指定解析评估上下文
Expression parseExpression(String expressionString, ParserContext context) throws ParseException;
}
实现类 TemplateAwareExpressionParser 增加了对模板的解析支持。
<