变量 (Variables)
你可以在表达式中使用 #variableName 语法来引用变量。变量是通过 EvaluationContext 实现类中的 setVariable() 方法设置的。
变量命名规则
- 变量名必须以字母(定义如下)、下划线(
_)或美元符号($)开头。 - 变量名必须由一个或多个受支持的字符组成:
- 字母:任何
java.lang.Character.isLetter(char)返回true的字符。- 这包括
A到Z、a到z、ü、ñ、é等字母,以及来自其他字符集(如中文、日文、西里尔文等)的字母。
- 这包括
- 数字:
0到9。 - 下划线:
_。 - 美元符号:
$。
- 字母:任何
建议
在 EvaluationContext 中设置变量或根上下文对象时,建议将变量或根上下文对象的类型设置为 public。
否则,涉及非 public 类型的变量或根对象的某些 SpEL 表达式可能会在评估或编译时失败。
警告
由于变量与评估上下文中的函数共享公共命名空间,因此必须小心确保变量名和函数名不会发生冲突。
以下示例演示了如何使用变量:
java
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
// 设置变量 "newName"
context.setVariable("newName", "Mike Tesla");
// 使用 #newName 引用变量并赋值给 name 属性
parser.parseExpression("name = #newName").getValue(context, tesla);
System.out.println(tesla.getName()); // "Mike Tesla"kotlin
val tesla = Inventor("Nikola Tesla", "Serbian")
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
// 设置变量 "newName"
context.setVariable("newName", "Mike Tesla")
// 使用 #newName 引用变量
parser.parseExpression("name = #newName").getValue(context, tesla)
println(tesla.name) // "Mike Tesla"#this 和 #root 变量
#this 变量始终有定义,它指向当前的评估对象(用于解析非限定引用的对象)。#root 变量也始终有定义,它指向根上下文对象。虽然 #this 可能会随着表达式组件的评估而改变,但 #root 始终指向根对象。
以下示例展示了如何结合集合选择 (Collection Selection)使用 #this 变量。
java
// 创建质数列表
List<Integer> primes = List.of(2, 3, 5, 7, 11, 13, 17);
// 创建解析器并设置变量 'primes'
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
context.setVariable("primes", primes);
// 从列表中选择所有大于 10 的质数(使用选择语法 .?[...])
// 在选择闭包内部,#this 指向当前遍历的元素
String expression = "#primes.?[#this > 10]";
// 评估结果为包含 [11, 13, 17] 的列表
List<Integer> primesGreaterThanTen =
parser.parseExpression(expression).getValue(context, List.class);kotlin
// 创建质数列表
val primes = listOf(2, 3, 5, 7, 11, 13, 17)
// 创建解析器并设置变量 'primes'
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
context.setVariable("primes", primes)
// 选择所有大于 10 的质数
val expression = "#primes.?[#this > 10]"
// 评估结果为列表 [11, 13, 17]
val primesGreaterThanTen = parser.parseExpression(expression)
.getValue(context) as List<Int>以下示例展示了如何结合集合投影 (Collection Projection)同时使用 #this 和 #root 变量。
java
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
// 创建一个发明家作为根上下文对象
Inventor tesla = new Inventor("Nikola Tesla");
tesla.setInventions("Telephone repeater", "Tesla coil transformer");
// 遍历根对象(#root)的所有发明(inventions),
// 生成格式为 "<发明家姓名> invented the <发明名称>." 的字符串列表。
// 在投影闭包内部:
// #root 指向 tesla 对象
// #this 指向当前正在处理的单个发明字符串
String expression = "#root.inventions.![#root.name + ' invented the ' + #this + '.']";
// 评估结果列表包含:
// "Nikola Tesla invented the Telephone repeater."
// "Nikola Tesla invented the Tesla coil transformer."
List<String> results = parser.parseExpression(expression)
.getValue(context, tesla, List.class);kotlin
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
val tesla = Inventor("Nikola Tesla")
tesla.setInventions("Telephone repeater", "Tesla coil transformer")
// 使用投影语法 ![...]
val expression = "#root.inventions.![#root.name + ' invented the ' + #this + '.']"
val results = parser.parseExpression(expression)
.getValue(context, tesla, List::class.java)补充教学
1. #this vs #root:深度理解
这是 SpEL 中最核心的概念之一,容易混淆。你可以把它们类比为:
#root:类似于整个程序的“全局根节点”,无论你进入多深的嵌套结构,它永远不变。#this:类似于“当前上下文句柄”。当你进入一个集合的某种操作(如筛选?[]或投影![])时,#this会自动切换为当前正在处理的那个元素。
2. 为什么变量名要加 #?
这是为了区分 属性访问 和 变量访问:
name:SpEL 会去根对象中寻找名为name的属性(调用getName())。#name:SpEL 会去EvaluationContext的变量池中寻找名为name的变量。 这种明确的区分让表达式在处理复杂逻辑时不会产生歧义。
3. 多语言支持的魅力
正如文档提到的,SpEL 支持 Unicode 变量名。虽然在生产环境中不建议过度使用,但在某些本地化场景下,它允许你写出非常易读的代码:
java
context.setVariable("用户名", "张三");
parser.parseExpression("'你好,' + #用户名").getValue(context); // 你好,张三4. 共享命名空间带来的冲突风险
由于变量和自定义函数(见下一章)共享同一个存储空间,如果你注册了一个名为 check 的函数,又设置了一个名为 #check 的变量,后设置的内容可能会覆盖前者。 最佳实践:
- 函数建议使用动词开头(如
#isAdult)。 - 变量建议使用名词(如
#user)。
5. 常见预定义变量
除了 #this 和 #root,在 Spring 的不同子项目中,框架会为你预定义很多有用的变量:
- Spring Security:提供
#principal(当前用户)、#authentication(认证信息)。 - Spring Data:提供
#entityName(实体名)等。 了解这些预定义变量能让你在配置这些框架时如鱼得水。