表达式模板 (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() 方法的第二个参数类型为 ParserContext。ParserContext 接口用于影响表达式的解析方式,以支持表达式模板功能。上例中使用的 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 进行计算。
- Spring 会先解析
4. 实战场景:动态消息生成
表达式模板非常适合用于生成根据上下文动态变化的文本,例如:
- 邮件模板:
"尊敬的 #{#user.name},您的验证码是 #{#code}"。 - 日志打印:在 AOP 层拦截方法,根据方法参数动态构建日志字符串:
"用户 #{#this[0]} 正在尝试删除 ID 为 #{#this[1]} 的订单".
5. 性能提示
由于模板解析涉及对原始字符串的扫描、拆分和对多条子表达式的独立评估,其开销比单一的 SpEL 表达式略高。
- 优化:如果模板是固定的,建议将其解析后的
Expression对象缓存起来,避免每次执行都重新parse。