Spring Expression Language能否在对象图中搜索匹配节点并返回单个/全部结果?
在SpEL中实现对象图全局搜索的方案
针对你的问题:SpEL本身没有原生的递归遍历对象图的语法,但可以通过自定义SpEL函数的方式,实现全局搜索第一个匹配节点或所有匹配节点的需求,以下是具体实现方案(基于Spring Boot 3.0.11对应Spring 6.0.x版本):
1. 实现全局搜索并返回第一个匹配节点
通过编写递归查找属性的工具方法,将其注册为SpEL自定义函数,即可在表达式中调用实现全局搜索。
步骤1:编写属性查找工具类
import java.lang.reflect.Field; import java.util.Iterable; public class SpelPropertySearcher { // 递归查找第一个匹配的属性值 public static Object findFirst(Object root, String propertyName) throws IllegalAccessException { if (root == null) return null; // 遍历当前对象的所有字段 Field[] fields = root.getClass().getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); // 匹配目标属性,直接返回值 if (field.getName().equals(propertyName)) { return field.get(root); } Object fieldValue = field.get(root); // 跳过基本类型、字符串,递归处理引用类型 if (fieldValue != null && !field.getType().isPrimitive() && !field.getType().equals(String.class)) { // 处理集合类,遍历每个元素递归查找 if (fieldValue instanceof Iterable<?>) { for (Object element : (Iterable<?>) fieldValue) { Object result = findFirst(element, propertyName); if (result != null) return result; } } else { // 处理单个引用对象,递归查找 Object result = findFirst(fieldValue, propertyName); if (result != null) return result; } } } return null; } }
步骤2:在SpEL中注册并使用函数
BookingEvent event = getEvent(); ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); // 注册自定义查找函数 context.registerFunction( "findFirst", SpelPropertySearcher.class.getMethod("findFirst", Object.class, String.class) ); // 搜索整个event对象中的第一个employeeId Expression exp = parser.parseExpression("#findFirst(this, 'employeeId')"); String firstEmployeeId = (String) exp.getValue(event, context); Assertions.assertEquals("EM1", firstEmployeeId); // 仅在metadata下搜索第一个employeeId exp = parser.parseExpression("#findFirst(metadata, 'employeeId')"); firstEmployeeId = (String) exp.getValue(event, context);
2. 返回所有匹配的属性值
同样通过自定义函数,修改工具方法为收集所有匹配结果:
步骤1:扩展工具类的全量查找方法
import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Iterable; import java.util.List; public class SpelPropertySearcher { // 递归查找所有匹配的属性值 public static List<Object> findAll(Object root, String propertyName) throws IllegalAccessException { List<Object> results = new ArrayList<>(); if (root == null) return results; Field[] fields = root.getClass().getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); if (field.getName().equals(propertyName)) { results.add(field.get(root)); } Object fieldValue = field.get(root); if (fieldValue != null && !field.getType().isPrimitive() && !field.getType().equals(String.class)) { if (fieldValue instanceof Iterable<?>) { for (Object element : (Iterable<?>) fieldValue) { results.addAll(findAll(element, propertyName)); } } else { results.addAll(findAll(fieldValue, propertyName)); } } } return results; } }
步骤2:注册并调用全量查找函数
// 注册全量查找函数 context.registerFunction( "findAll", SpelPropertySearcher.class.getMethod("findAll", Object.class, String.class) ); // 搜索整个event对象中的所有employeeId Expression exp = parser.parseExpression("#findAll(this, 'employeeId')"); List<String> allEmployeeIds = (List<String>) exp.getValue(event, context); Assertions.assertTrue(allEmployeeIds.contains("EM1"));
3. 注意事项
- 上述工具类基于字段反射实现,如果需要遵循JavaBean规范(通过getter方法访问属性),可以修改为遍历类的Method,匹配对应getter后调用。
- 可扩展逻辑支持数组、Map等类型的遍历,覆盖更多场景。
- 如果需要在Spring Data JPA的
@Query中全局使用这些函数,可以通过EvaluationContextExtension注册为全局扩展,无需每次手动注册。
内容的提问来源于stack exchange,提问作者tuk
相关产品推荐
相关产品推荐

