Spring Expression Language (SpEL) 详解:功能强大的表达式引擎
在现代 Java 开发中,Spring 框架几乎无处不在。而作为 Spring 的核心组件之一,Spring Expression Language (SpEL) 提供了一种强大且灵活的方式来在运行时查询和操作对象图。无论是配置、条件判断还是动态计算,SpEL 都能大显身手。
本文将系统地梳理 SpEL 的核心语法与功能,帮助你全面掌握这一利器。
📌 什么是 SpEL?
Spring Expression Language(简称 SpEL)是一种功能丰富的表达式语言,支持在运行时对对象进行查询和操作。它借鉴了其他语言(如 OGNL、JRuby、Groovy)的特性,提供了统一的语法来处理属性访问、方法调用、集合操作、条件逻辑等。
✅ 应用场景:
@Value注解中的动态值注入- Spring Security 的权限表达式
- Spring Data JPA 中的查询条件
- Bean 定义中的条件逻辑
🔤 4.3.1 字面量表达式(Literal Expressions)
SpEL 支持多种基本类型的字面量:
| 类型 | 示例 |
|---|---|
| 字符串 | 'Hello World'(单引号) |
| 数值 | 6.0221415E+23, 0x7FFFFFFF |
| 布尔值 | true, false |
| null | null |
ExpressionParser parser = new SpelExpressionParser();String hello = parser.parseExpression("'Hello World'").getValue(String.class);
double avogadro = parser.parseExpression("6.0221415E+23").getValue(Double.class);
int maxInt = parser.parseExpression("0x7FFFFFFF").getValue(Integer.class);
Boolean isTrue = parser.parseExpression("true").getValue(Boolean.class);
Object nullVal = parser.parseExpression("null").getValue();
📌 注意:字符串使用单引号,若需包含单引号,则用两个单引号表示:'It''s a test'
🧱 4.3.2 属性、数组、列表、Map 与索引器
SpEL 可以通过点号(.)访问对象属性,支持嵌套访问。
// 获取 Tesla 的出生年份
int year = parser.parseExpression("Birthdate.Year + 1900").getValue(context, Integer.class);// 获取出生城市
String city = parser.parseExpression("placeOfBirth.City").getValue(context, String.class);
数组和列表访问(使用 [])
// 获取 inventions 数组的第4个元素
String invention = parser.parseExpression("inventions[3]").getValue(context, tesla, String.class);// 获取成员列表的第一个成员姓名
String name = parser.parseExpression("Members[0].Name").getValue(context, ieee, String.class);
Map 访问(使用 ['key'])
// 获取 officers 中 president 的信息
Inventor president = parser.parseExpression("Officers['president']").getValue(societyContext, Inventor.class);// 修改 advisor 的国家
parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(societyContext, "Croatia");
📦 4.3.3 内联列表(Inline Lists)
使用 {} 创建列表:
// 创建一个数字列表
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);// 嵌套列表
List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
✅ {} 表示空列表。若内容为常量,SpEL 会优化为常量集合以提升性能。
🗃️ 4.3.4 内联映射(Inline Maps)
使用 {key:value} 创建 Map:
Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context);Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context);
✅ {:} 表示空 Map。键名可不加引号,但推荐加引号避免歧义。
🔺 4.3.5 数组构造
使用 new 关键字创建数组:
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);// 初始化数组
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);// 多维数组
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
⚠️ 目前不支持多维数组的初始化器。
⚙️ 4.3.6 方法调用
支持标准 Java 方法调用语法:
// 字符串截取
String bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class); // "bc"// 调用自定义方法
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(societyContext, Boolean.class);
➕ 4.3.7 运算符
比较运算符
| 符号 | 文本形式 | 含义 |
|---|---|---|
== | eq | 等于 |
!= | ne | 不等于 |
< | lt | 小于 |
<= | le | 小于等于 |
> | gt | 大于 |
>= | ge | 大于等于 |
boolean isEqual = parser.parseExpression("2 == 2").getValue(Boolean.class); // true
boolean isGreater = parser.parseExpression("'black' < 'block'").getValue(Boolean.class); // true
📌 关于 null 的比较:
X > null恒为trueX < null恒为false- 推荐与
0比较而非null
其他运算符
instanceof:类型检查matches:正则匹配
boolean isInt = parser.parseExpression("'xyz' instanceof T(Integer)").getValue(Boolean.class); // false
boolean matches = parser.parseExpression("'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class); // true
逻辑运算符
| 运算符 | 说明 |
|---|---|
and / && | 与 |
or / ` | |
not / ! | 非 |
boolean result = parser.parseExpression("true and false").getValue(Boolean.class); // false
数学运算符
支持 +, -, *, /, %, ^(幂),遵循标准优先级。
int result = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21
🧪 4.3.8 类型操作(T 操作符)
使用 T() 获取 Class 对象或调用静态方法:
Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);
Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);// 静态方法比较
boolean result = parser.parseExpression("T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR").getValue(Boolean.class);
⚠️ 除基本类型和
String外,其他类需使用全限定名。
🏗️ 4.3.9 构造函数调用
使用 new 调用构造函数:
Inventor einstein = parser.parseExpression("new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')").getValue(Inventor.class);
📦 4.3.10 变量(Variables)
使用 #variableName 引用变量,通过 EvaluationContext 设置:
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
context.setVariable("newName", "Mike Tesla");parser.parseExpression("Name = #newName").getValue(context, tesla);
特殊变量
#this:当前上下文对象#root:根上下文对象(始终不变)
// 找出大于 10 的质数
context.setVariable("primes", Arrays.asList(2,3,5,7,11,13,17));
List<Integer> result = (List<Integer>) parser.parseExpression("#primes.?[#this > 10]").getValue(context);
// [11, 13, 17]
🛠️ 4.3.11 自定义函数
可注册 Java 方法作为 SpEL 函数:
// 假设有一个静态方法
public static String reverseString(String input) { ... }Method method = StringUtils.class.getDeclaredMethod("reverseString", String.class);
context.setVariable("reverseString", method);String result = parser.parseExpression("#reverseString('hello')").getValue(context, String.class);
// "olleh"
🏢 4.3.12 Bean 引用
使用 @beanName 引用 Spring 容器中的 Bean:
context.setBeanResolver(new MyBeanResolver());
Object bean = parser.parseExpression("@something").getValue(context);
@beanName:引用普通 Bean&beanName:引用 FactoryBean 本身
❓ 4.3.13 三元运算符(If-Then-Else)
标准的三元操作:
String result = parser.parseExpression("false ? 'trueExp' : 'falseExp'").getValue(String.class);
// "falseExp"
👑 4.3.14 Elvis 操作符
Groovy 风格的简化三元操作,用于提供默认值:
String name = parser.parseExpression("name ?: 'Unknown'").getValue(inventor, String.class);// 实际应用:注入系统属性或默认值
@Value("#{systemProperties['pop3.port'] ?: 25}")
private int port;
🛡️ 4.3.15 安全导航操作符(Safe Navigation)
防止 NullPointerException,对象为 null 时返回 null 而非抛异常:
String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String.class);
// 若 PlaceOfBirth 为 null,返回 null 而非报错
🔍 4.3.16 集合选择(Collection Selection)
使用 .?[expression] 过滤集合:
// 获取所有塞尔维亚发明家
List<Inventor> serbs = (List<Inventor>) parser.parseExpression("Members.?[Nationality == 'Serbian']").getValue(societyContext);// 获取 map 中 value < 27 的条目
Map filtered = parser.parseExpression("map.?[value < 27]").getValue();
.^[]:返回第一个匹配项.$[]:返回最后一个匹配项
🌐 4.3.17 集合投影(Collection Projection)
使用 .![expression] 对集合每个元素执行表达式,生成新集合:
// 获取所有成员的出生城市
List cities = (List) parser.parseExpression("Members.![placeOfBirth.city]").getValue();
// ['Smiljan', 'Idvor']
🧩 4.3.18 表达式模板(Expression Templating)
混合文本与表达式,使用 #{} 作为占位符:
String result = parser.parseExpression("random number is #{T(java.lang.Math).random()}",new TemplateParserContext()).getValue(String.class);
// "random number is 0.7038186818312008"
自定义 ParserContext 控制模板解析行为:
public class TemplateParserContext implements ParserContext {public String getExpressionPrefix() { return "#{"; }public String getExpressionSuffix() { return "}"; }public boolean isTemplate() { return true; }
}
🏁 总结
SpEL 是 Spring 生态中不可或缺的一部分,其功能远超简单的值注入。掌握以下关键点,能让你在开发中游刃有余:
| 功能 | 语法示例 |
|---|---|
| 属性访问 | obj.property |
| 集合索引 | list[0], map['key'] |
| 内联结构 | {1,2,3}, {name:'Tom'} |
| 方法调用 | 'abc'.substring(1) |
| 三元/Elvis | a ?: b, cond ? x : y |
| 安全导航 | obj?.prop |
| 集合操作 | .?[filter], .![proj] |
| 模板 | #{expr} |
💡 建议:在使用 SpEL 时,合理使用
EvaluationContext来管理变量、函数和类型解析,避免硬编码,提升表达式的可维护性。
📚 参考资料:
- Spring Framework 官方文档 - SpEL
- 示例中使用的
Inventor,PlaceOfBirth,Society类定义(见原文末尾)
掌握 SpEL,让你的 Spring 应用更灵活、更强大!🚀
