当前位置: 首页 > news >正文

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
nullnull
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 恒为 true
  • X < 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)
三元/Elvisa ?: b, cond ? x : y
安全导航obj?.prop
集合操作.?[filter], .![proj]
模板#{expr}

💡 建议:在使用 SpEL 时,合理使用 EvaluationContext 来管理变量、函数和类型解析,避免硬编码,提升表达式的可维护性。


📚 参考资料

  • Spring Framework 官方文档 - SpEL
  • 示例中使用的 Inventor, PlaceOfBirth, Society 类定义(见原文末尾)

掌握 SpEL,让你的 Spring 应用更灵活、更强大!🚀

http://www.dtcms.com/a/532096.html

相关文章:

  • LeetCode:773. 滑动谜题
  • MATLAB基于类别加权灰靶决策的教学评价研究
  • C16可变参数模板函数和普通函数模板
  • 网站建设规划设计方案建设部门电工证查询网站
  • ​​lseek​​的“时空跳跃”:从获取大小到制造“文件空洞”
  • 技术演进中的开发沉思-151 java-servlet:会话管理
  • 【IO多路转接】IO 多路复用之 select:从接口解析到服务器实战
  • 淄博周村学校网站建设定制wordpress文章和页面
  • Multitouch for mac 触控板多点手势创建
  • SIGCHLD:进程终止与僵尸进程清理的关键
  • 数据结构(10)
  • 南皮做网站的团队管理的七个要点
  • Mysql的数据备份和高可用
  • 【Kotlin】数组集合常用扩展函数
  • css新增盒子属性——尺寸调节
  • 做阿里国际网站会有成效吗上海网站建设公司招人
  • 【课堂笔记】概率论-3
  • 【硬件基础篇】:CPU如何被制造出来
  • 面向模块的综合技术之控制集优化(七)
  • 做网站广告软件网站系统设计目标
  • 使用稀疏采样方法减轻汽车雷达干扰——论文阅读
  • 阮一峰《TypeScript 教程》学习笔记——d.ts 类型声明文件
  • Spring AOP:横切关注点的优雅解决方案
  • 如何申请网站空间和注册域名鞋子软文推广300字
  • 基于AutoDL远端服务器在pycharm复现:具身智能论文pai0
  • 如何看访问网站的dns网站开发难不难
  • 数据结构·堆
  • 阮一峰《TypeScript 教程》学习笔记——类型映射
  • 需要做网站建设和推广网站地图插件
  • PyCharm 设置 Tabs and Indents