Skip to content

表达式模板 (Expression Templating)

表达式模板允许将字面文本与一个或多个计算块混合使用。

每个计算块都由你可以定义的起始和结束字符括起来。一个常见的选择是使用 #{ } 作为分隔符,如下例所示:

java
String randomPhrase = parser.parseExpression(
		"random number is #{T(java.lang.Math).random()}",
		new TemplateParserContext()).getValue(String.class);

// 评估结果类似于 "random number is 0.7038186818312008"
kotlin
val randomPhrase = parser.parseExpression(
		"random number is #{T(java.lang.Math).random()}",
		TemplateParserContext()).getValue(String::class.java)

// 评估结果类似于 "random number is 0.7038186818312008"

该字符串的求值过程是将字面文本 'random number is '#{ } 分隔符内的表达式评估结果(在本例中是调用 random() 方法的结果)进行拼接。

parseExpression() 方法的第二个参数类型为 ParserContextParserContext 接口用于影响表达式的解析方式,以支持表达式模板功能。上例中使用的 TemplateParserContext 类位于 org.springframework.expression.common 包中,它是 ParserContext 的一个实现,其默认配置将前缀和后缀分别设置为 #{}


补充教学

1. 核心接口:ParserContext

在 SpEL 中,如果你直接解析一个字符串,它会被视为一整条表达式。但如果你需要像 JSP、Thymeleaf 或 SQL 动态语句那样在文本中嵌入逻辑,就需要 ParserContext 来告诉解析器:

  • isTemplate():是否开启模板模式。
  • getExpressionPrefix():块的开始标识(如 #{)。
  • getExpressionSuffix():块的结束标识(如 })。

2. 自定义分隔符

虽然 #{ } 是行业标准,但有时为了避免与某些解析引擎(如 Shell 脚本或某些前端框架)冲突,你可能需要自定义分隔符。

java
// 自定义为 $[ ] 分隔符
ParserContext customContext = new ParserContext() {
    public boolean isTemplate() { return true; }
    public String getExpressionPrefix() { return "$["; }
    public String getExpressionSuffix() { return "]"; }
};

3. 与属性占位符 ${...} 的“三方混战”

在 Spring 开发中,你经常会看到这两种符号:

  • ${property.name}属性占位符。仅负责从配置文件中读取值,它是纯文本替换,不具备逻辑计算能力。
  • #{expression}SpEL 表达式。具备强大的计算、方法调用、对象访问能力。
  • 混合使用@Value("#{'${app.prefix}' + '_suffix'}")
    • Spring 会先解析 ${...} 替换为文本,然后再将结果作为 SpEL 进行计算。

4. 实战场景:动态消息生成

表达式模板非常适合用于生成根据上下文动态变化的文本,例如:

  • 邮件模板"尊敬的 #{#user.name},您的验证码是 #{#code}"
  • 日志打印:在 AOP 层拦截方法,根据方法参数动态构建日志字符串:"用户 #{#this[0]} 正在尝试删除 ID 为 #{#this[1]} 的订单".

5. 性能提示

由于模板解析涉及对原始字符串的扫描、拆分和对多条子表达式的独立评估,其开销比单一的 SpEL 表达式略高。

  • 优化:如果模板是固定的,建议将其解析后的 Expression 对象缓存起来,避免每次执行都重新 parse

Based on Spring Framework.