Skip to content

Spring 表达式语言 (SpEL)

Spring 表达式语言(简称“SpEL”)是一种强大的表达式语言,支持在运行时查询和操作对象图。其语言语法类似于 Jakarta 表达式语言,但提供了额外的功能,最显著的是方法调用和基本的字符串模板功能。

虽然市面上还有多种其他 Java 表达式语言(如 OGNL、MVEL 和 JBoss EL),但创建 Spring 表达式语言的初衷是为 Spring 社区提供一种得到良好支持且能跨 Spring 家族所有产品使用的统一表达式语言。它的语言特性驱动自 Spring 家族项目的需求,包括 Spring Tools IDE 插件中对代码补全的工具化支持。即便如此,SpEL 仍基于一套与具体技术无关的 API,如果需要,可以集成其他的表达式语言实现。

虽然 SpEL 是 Spring 家族中表达式评估(evaluation)的基础,但它并不直接与 Spring 绑定,可以独立使用。为了保证自包含性,本章中的许多示例将 SpEL 作为独立的表达式语言来使用。这需要创建一些引导性的基础设施类,例如解析器(parser)。大多数 Spring 用户不需要处理这些基础设施,相反,只需编写评估用的表达式字符串即可。这种典型用法的示例包括将 SpEL 集成到创建 XML 或基于注解的 Bean 定义中,详见表达式对 Bean 定义的支持

本章涵盖了该表达式语言的特性、其 API 以及语言语法。在多个地方,我们将使用 InventorSociety 类作为表达式评估的目标对象。这些类声明以及用于填充它们的数据列在本章末尾。

该表达式语言支持以下功能:

  • 字面量表达式 (Literal expressions)
  • 属性、数组、列表和 Map 的访问
  • 内联列表 (Inline lists)
  • 内联 Map (Inline maps)
  • 数组构造
  • 关系运算符
  • 正则表达式
  • 逻辑运算符
  • 字符串操作符
  • 数学运算符
  • 赋值操作
  • 类型表达式
  • 方法调用
  • 构造函数调用
  • 变量
  • 用户自定义函数
  • Bean 引用
  • 三元运算符、Elvis 运算符和安全导航运算符
  • 集合投影 (Collection projection)
  • 集合选择 (Collection selection)
  • 模板化表达式 (Templated expressions)

补充教学

1. SpEL 的核心三要素

在独立使用 SpEL 时,你会经常看到以下三个核心类。理解它们有助于掌握 SpEL 的底层运作:

  • ExpressionParser:解析器,负责将字符串表达式解析为可执行的 Expression 对象。常用实现是 SpelExpressionParser
  • EvaluationContext:评估上下文。它像是一个容器,存放着表达式运行所需的变量、函数、根对象(Root Object)以及类型转换器。常用实现是 StandardEvaluationContext
  • Expression:解析后的表达式对象。调用它的 getValue() 方法即可获得结果。

2. SpEL 与 Spring Boot 的深度绑定点

作为应用开发者,你最常在以下场景遇到 SpEL:

  • @Value 注解@Value("#{systemProperties['user.home']}") —— 这里使用 #{...} 包裹的就是 SpEL。注意区分于 ${...}(属性占位符)。
  • 条件注解@ConditionalOnExpression("'${my.feature.enabled}' == 'true'")
  • Spring Security@PreAuthorize("hasRole('ADMIN') and #user.id == principal.id") —— 安全框架大量使用 SpEL 进行权限判定。
  • 缓存注解@Cacheable(value="books", key="#isbn")

3. 安全警示:SpEL 注入

由于 SpEL 功能过于强大(甚至可以调用 java.lang.Runtime 执行系统命令),如果你的应用程序允许评估来自用户输入的未过滤字符串,就会面临 SpEL 注入攻击 的风险。

  • 最佳实践
    • 永远不要对不受信任的用户输入直接调用 Expression.getValue()
    • 在处理不确定来源的表达式时,使用权限受限的评估上下文,例如 SimpleEvaluationContext(它默认不支持自定义静态方法、构造函数等危险操作),而非 StandardEvaluationContext

4. 性能优化:缓存表达式

解析解析字符串表达式是一个相对昂贵的操作。如果你在循环中或高频接口中使用 SpEL,请务必 缓存 Expression 对象,而不是每次都解析字符串。

Based on Spring Framework.