Skip to content

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 生态系统的表达式引擎,通过本篇教程我们掌握了:

  1. SpEL 核心语法和基本用法 ✅
  2. 自定义评估上下文和函数 ✅
  3. 属性访问器高级扩展 ✅
  4. 实际应用场景与最佳实践 ✅

下一步学习建议

  • 探索 SpEL 在 Spring Security 权限表达式中的应用
  • 学习 Spring Data 中基于 SpEL 的查询机制
  • 实践在自定义注解中使用 SpEL 表达式

IMPORTANT

在微服务架构中,合理使用 SpEL 可以大幅减少硬编码逻辑,但需注意避免过度复杂的表达式,保持代码可维护性。