Skip to content

Spring Expression Language (SpEL) - Collection Selection 集合选择

🎯 什么是 Collection Selection?

Collection Selection(集合选择)是 Spring Expression Language (SpEL) 中一个强大的功能,它允许我们从一个源集合中筛选出符合特定条件的元素,并创建一个新的集合。

TIP

想象一下你在一个大型图书馆中寻找特定类型的书籍。Collection Selection 就像是一个智能的图书管理员,能够根据你的条件(比如"作者是中国人"或"出版年份在2020年之后")快速筛选出符合要求的书籍列表。

🤔 为什么需要 Collection Selection?

在实际开发中,我们经常遇到这样的场景:

  • 从用户列表中筛选出活跃用户
  • 从订单列表中找出今天的订单
  • 从商品列表中筛选出特定价格范围的商品
  • 从配置项中选择启用的功能

如果没有 Collection Selection,我们需要编写大量的循环和条件判断代码:

kotlin
// 传统方式:需要手动循环筛选
fun filterActiveUsers(users: List<User>): List<User> {
    val activeUsers = mutableListOf<User>()
    for (user in users) {
        if (user.isActive && user.lastLoginDays < 30) { 
            activeUsers.add(user) 
        }
    }
    return activeUsers 
}
kotlin
// 使用 SpEL Collection Selection:一行搞定
val activeUsers = parser.parseExpression(
    "users.?[isActive && lastLoginDays < 30]"
).getValue(context) as List<User>

📝 基本语法

Collection Selection 使用以下语法模式:

.?[selectionExpression]  // 选择所有符合条件的元素
.^[selectionExpression]  // 选择第一个符合条件的元素  
.$[selectionExpression]  // 选择最后一个符合条件的元素

IMPORTANT

  • ? 表示选择所有匹配的元素
  • ^ 表示选择第一个匹配的元素
  • $ 表示选择最后一个匹配的元素

🛠️ 实际应用示例

1. 用户管理系统

kotlin
@Service
class UserService {
    
    private val parser = SpelExpressionParser()
    
    data class User(
        val name: String,
        val age: Int,
        val department: String,
        val isActive: Boolean,
        val salary: Double
    )
    
    fun demonstrateCollectionSelection() {
        val users = listOf(
            User("张三", 25, "技术部", true, 8000.0),
            User("李四", 30, "销售部", false, 6000.0),
            User("王五", 28, "技术部", true, 9000.0),
            User("赵六", 35, "人事部", true, 7000.0)
        )
        
        val context = StandardEvaluationContext().apply {
            setVariable("users", users) 
        }
        
        // 筛选技术部的活跃用户
        val techActiveUsers = parser.parseExpression(
            "#users.?[department == '技术部' && isActive]"
        ).getValue(context) as List<User>
        
        println("技术部活跃用户: ${techActiveUsers.map { it.name }}")
        // 输出: [张三, 王五]
        
        // 找出工资最高的用户(选择第一个)
        val highestSalaryUser = parser.parseExpression(
            "#users.^[salary > 8500]"
        ).getValue(context) as User?
        
        println("高薪用户: ${highestSalaryUser?.name}")
        // 输出: 王五
    }
}

2. 电商订单处理

kotlin
@RestController
@RequestMapping("/api/orders")
class OrderController {
    
    private val parser = SpelExpressionParser()
    
    data class Order(
        val id: String,
        val customerId: String,
        val amount: Double,
        val status: String,
        val createTime: LocalDateTime
    )
    
    @GetMapping("/filter")
    fun filterOrders(@RequestParam status: String?): ResponseEntity<List<Order>> {
        val orders = getOrdersFromDatabase() // 假设从数据库获取订单
        
        val context = StandardEvaluationContext().apply {
            setVariable("orders", orders)
            setVariable("targetStatus", status ?: "COMPLETED") 
        }
        
        // 使用 SpEL 筛选指定状态的订单
        val filteredOrders = parser.parseExpression(
            "#orders.?[status == #targetStatus]"
        ).getValue(context) as List<Order>
        
        return ResponseEntity.ok(filteredOrders)
    }
    
    @GetMapping("/high-value")
    fun getHighValueOrders(): ResponseEntity<List<Order>> {
        val orders = getOrdersFromDatabase()
        
        val context = StandardEvaluationContext().apply {
            setVariable("orders", orders)
        }
        
        // 筛选高价值订单(金额大于1000)
        val highValueOrders = parser.parseExpression(
            "#orders.?[amount > 1000 && status == 'COMPLETED']"
        ).getValue(context) as List<Order>
        
        return ResponseEntity.ok(highValueOrders)
    }
    
    private fun getOrdersFromDatabase(): List<Order> {
        // 模拟数据库数据
        return listOf(
            Order("001", "user1", 1500.0, "COMPLETED", LocalDateTime.now().minusDays(1)),
            Order("002", "user2", 800.0, "PENDING", LocalDateTime.now().minusHours(2)),
            Order("003", "user1", 2000.0, "COMPLETED", LocalDateTime.now().minusDays(3))
        )
    }
}

3. Map 集合的选择操作

kotlin
@Component
class ConfigurationManager {
    
    private val parser = SpelExpressionParser()
    
    fun demonstrateMapSelection() {
        // 系统配置映射
        val systemConfig = mapOf(
            "maxUsers" to 100,
            "timeout" to 30,
            "retryCount" to 3,
            "bufferSize" to 1024,
            "debugLevel" to 2
        )
        
        val context = StandardEvaluationContext().apply {
            setVariable("config", systemConfig) 
        }
        
        // 筛选值小于50的配置项
        val smallValueConfigs = parser.parseExpression(
            "#config.?[value < 50]"
        ).getValue(context) as Map<String, Int>
        
        println("小值配置项: $smallValueConfigs")
        // 输出: {timeout=30, retryCount=3, debugLevel=2}
        
        // 获取第一个值大于500的配置项
        val firstLargeConfig = parser.parseExpression(
            "#config.^[value > 500]"
        ).getValue(context) as Map.Entry<String, Int>?
        
        println("第一个大值配置: ${firstLargeConfig?.key} = ${firstLargeConfig?.value}")
        // 输出: bufferSize = 1024
    }
}

🔄 集合选择的工作流程

🎯 支持的集合类型

Collection Selection 支持多种集合类型:

集合类型说明示例
Array数组array.?[condition]
List列表list.?[condition]
Set集合set.?[condition]
Map映射map.?[key == 'target']
Iterable可迭代对象iterable.?[condition]

NOTE

对于 Map 类型,筛选表达式会针对每个 Map.Entry 对象进行评估,你可以使用 keyvalue 属性来访问键值对。

⚡ 性能优化建议

性能注意事项

Collection Selection 会遍历整个集合,对于大型集合可能存在性能问题。

kotlin
@Service
class OptimizedCollectionService {
    
    private val parser = SpelExpressionParser()
    
    // ❌ 不推荐:对大集合进行复杂筛选
    fun inefficientFiltering(largeUserList: List<User>): List<User> {
        val context = StandardEvaluationContext().apply {
            setVariable("users", largeUserList) // 假设有10万条数据
        }
        
        return parser.parseExpression(
            "#users.?[isActive && department == '技术部' && salary > 8000]"
        ).getValue(context) as List<User>
    }
    
    // ✅ 推荐:先在数据库层面筛选,再使用 SpEL 进行细粒度过滤
    fun efficientFiltering(): List<User> {
        // 先通过数据库查询减少数据量
        val preFilteredUsers = userRepository.findActiveUsersByDepartment("技术部") 
        
        val context = StandardEvaluationContext().apply {
            setVariable("users", preFilteredUsers) // 数据量已大幅减少
        }
        
        return parser.parseExpression(
            "#users.?[salary > 8000]"
        ).getValue(context) as List<User>
    }
}

🔒 安全集合选择

SpEL 还支持安全导航操作符,防止空指针异常:

kotlin
// 安全集合选择 - 即使 users 为 null 也不会抛异常
val safeResult = parser.parseExpression(
    "users?.?[isActive]"
).getValue(context) as List<User>?

📚 总结

Collection Selection 是 SpEL 中的一个强大功能,它能够:

简化代码:用一行表达式替代复杂的循环逻辑
提高可读性:筛选条件直观明了
支持多种集合:Array、List、Set、Map 等
灵活筛选:支持复杂的条件表达式
安全操作:支持安全导航,避免空指针异常

最佳实践

  1. 对于大型集合,优先在数据源层面进行预筛选
  2. 合理使用 ^$ 操作符获取首个或末个匹配元素
  3. 结合安全导航操作符 ?. 提高代码健壮性
  4. 在 Spring Boot 应用中,可以将常用的筛选表达式配置化管理

通过掌握 Collection Selection,你可以让集合操作变得更加优雅和高效! 🚀