Appearance
Spring Expression Language (SpEL) 全面教程
一、SpEL 核心概念与应用场景
1.1 什么是 SpEL?
Spring Expression Language (SpEL) 是 Spring 框架中的一种强大的表达式语言,允许在运行时查询和操作对象图。它类似于 JSP 的 EL 表达式,但功能更加强大,支持方法调用、基本字符串模板和集合操作等特性。
1.2 为什么需要 SpEL?
在 Spring Integration 等组件中,SpEL 提供了一种声明式方式来配置组件行为,避免了硬编码逻辑。例如:
kotlin
// 使用 SpEL 从消息中提取数据
@Transformer(inputChannel = "input", outputChannel = "output")
fun transform(@Payload payload: String, @Header("user") user: String) =
"Hello, $user! Your message: ${payload.toUpperCase()}"
主要优势:
- ✅ 运行时灵活性:动态解析表达式,无需重新编译
- ✅ 简化配置:减少样板代码,配置更简洁
- ✅ 统一表达式语法:跨 Spring 组件使用相同语法
1.3 基本表达式示例
在 Spring Integration 中,SpEL 默认以 Message
为根对象:
kotlin
// 访问消息负载
payload
// 访问消息头
headers['my.header']
// 访问负载属性
payload.user.name
// 组合表达式
"User: ${headers['user-id']}, Message: ${payload.substring(0,10)}"
二、SpEL 评估上下文定制
2.1 自定义 PropertyAccessor
通过自定义属性访问器扩展 SpEL 的能力:
kotlin
// 自定义 JSON 属性访问器
class JsonPropertyAccessor : PropertyAccessor {
override fun canRead(context: EvaluationContext?, target: Any?, name: String) =
target is JsonNode
override fun read(context: EvaluationContext?, target: Any?, name: String): TypedValue {
val node = target as JsonNode
return TypedValue(node.get(name) ?: throw SpelEvaluationException(...)
}
}
// 注册自定义访问器
@Bean
fun spelPropertyAccessorRegistrar() = SpelPropertyAccessorRegistrar().apply {
add(JsonPropertyAccessor())
add(MyCustomAccessor())
}
2.2 添加自定义函数
创建静态方法作为 SpEL 函数:
kotlin
// 定义工具类
object MySpelFunctions {
@JvmStatic
fun calculateDiscount(price: Double, discount: Double) = price * (1 - discount)
}
// 注册为 SpEL 函数
@Bean
fun discountFunction() = SpelFunctionFactoryBean(
MySpelFunctions::class.java, "calculateDiscount"
)
使用自定义函数:
kotlin
// 在表达式中使用
"#{calculateDiscount(payload.price, 0.2)}"
2.3 完整配置示例
使用 Kotlin DSL 配置评估上下文:
kotlin
@Configuration
class SpelConfig {
@Bean
fun integrationEvaluationContext(): IntegrationEvaluationContextFactoryBean {
return IntegrationEvaluationContextFactoryBean().apply {
propertyAccessors = listOf(
JsonPropertyAccessor(),
MapAccessor(),
ReflectivePropertyAccessor()
)
functions = mapOf(
"discount" to MySpelFunctions::class.java.getDeclaredMethod(
"calculateDiscount", Double::class.java, Double::class.java
)
)
}
}
}
三、SpEL 函数详解
3.1 自定义函数实现
两种注册方式对比:
kotlin
@Bean
fun xpathFunction() = SpelFunctionFactoryBean(
XPathUtils::class.java, "evaluate"
)
kotlin
@Bean
fun spelFunctions(): Map<String, Method> {
return mapOf(
"xpath" to XPathUtils::class.java.getMethod(
"evaluate", String::class.java, Object::class.java
)
)
}
3.2 内置函数使用技巧
3.2.1 #jsonPath 函数
kotlin
// 提取 JSON 数据
@Transformer(inputChannel = "input", outputChannel = "output")
fun process(@Payload payload: String) {
val author = parser.parseExpression(
"#jsonPath(payload, '$.store.book[0].author')"
).getValue(String::class.java)
// 使用结果...
}
依赖要求
使用 #jsonPath
需要添加依赖:
groovy
implementation("com.jayway.jsonpath:json-path:2.8.0")
3.2.2 #xpath 函数
kotlin
// 处理 XML 数据
@Filter(inputChannel = "xmlInput")
fun isValid(@Payload payload: Document): Boolean {
return parser.parseExpression(
"#xpath('//order/@status', payload) == 'completed'"
).getValue(Boolean::class.java) ?: false
}
四、属性访问器高级用法
4.1 自定义索引访问器
处理 JSON 数组索引:
kotlin
class JsonIndexAccessor : IndexAccessor {
override fun canRead(context: EvaluationContext?, target: Any?, index: Any): Boolean {
return target is ArrayNode && index is Int
}
override fun read(context: EvaluationContext?, target: Any?, index: Any): TypedValue {
val array = target as ArrayNode
val idx = index as Int
return TypedValue(array.get(idx))
}
}
4.2 注册访问器
kotlin
@Bean
fun spelPropertyAccessorRegistrar() = SpelPropertyAccessorRegistrar().apply {
add(JsonIndexAccessor()) // 索引访问器
add(JsonPropertyAccessor()) // 属性访问器
}
使用示例:
kotlin
// 访问 JSON 数组元素
"payload.books[0].title" // 使用自定义索引访问器
五、实际应用场景
5.1 HTTP 请求处理
kotlin
@Bean
fun httpInboundGateway() = Http.inboundGateway("/api/{id}")
.requestPayloadType<String>()
.headerExpression("deviceType", "#requestParams['device']")
.uriVariableExpression("id", "#pathVariables.id")
.replyTimeout(10_000)
.get()
5.2 动态路由
路由配置代码:
kotlin
@Bean
fun messageRouter(): HeaderValueRouter {
return HeaderValueRouter("messageType").apply {
setChannelMapping("VIP", "vipChannel")
setChannelMapping("STANDARD", "standardChannel")
setDefaultOutputChannel(defaultChannel)
}
}
六、最佳实践与常见问题
6.1 性能优化建议
- ⚡️ 缓存表达式:重复使用的表达式应预编译kotlin
private val expression = SpelExpressionParser().parseExpression("payload.size() > 10") fun checkSize(message: Message<*>): Boolean { return expression.getValue(evalContext, message, Boolean::class.java) ?: false }
- ⚠️ 避免复杂表达式:保持表达式简洁(不超过 3 层嵌套)
6.2 常见错误排查
kotlin
// 错误:未处理空值
headers['missing-header'].toUpperCase()
// 正确:使用安全导航操作符
headers['missing-header']?.toUpperCase() ?: "default"
错误现象 | 原因分析 | 解决方案 |
---|---|---|
SpelEvaluationException | 属性路径不存在 | 使用 ?. 安全导航操作符 |
性能下降 | 复杂表达式重复解析 | 预编译表达式 |
空指针异常 | 未处理可能为null的值 | 添加空值检查或默认值 |
6.3 调试技巧
启用 SpEL 日志:
properties
# application.properties
logging.level.org.springframework.expression=DEBUG
日志输出示例
log
DEBUG o.s.expression.spel.standard - Evaluating expression: 'payload.user.name'
DEBUG o.s.expression.spel.standard - Evaluated to: 'John Doe'
总结
SpEL 作为 Spring 生态系统的表达式引擎,通过本篇教程我们掌握了:
- SpEL 核心语法和基本用法 ✅
- 自定义评估上下文和函数 ✅
- 属性访问器高级扩展 ✅
- 实际应用场景与最佳实践 ✅
下一步学习建议
- 探索 SpEL 在 Spring Security 权限表达式中的应用
- 学习 Spring Data 中基于 SpEL 的查询机制
- 实践在自定义注解中使用 SpEL 表达式
IMPORTANT
在微服务架构中,合理使用 SpEL 可以大幅减少硬编码逻辑,但需注意避免过度复杂的表达式,保持代码可维护性。