Spring SpEL 如何通过自定义函数按需提供变量值实现懒加载
SpEL 变量懒加载落地实现方案
原有实现的问题根源
你之前尝试的context.setVariables(lazyloader)的写法无法生效,核心原因是SpEL解析变量时,只会从上下文预先初始化的变量键值对中做静态匹配,不会自动将变量名作为参数传给注册的Function做动态查找,自然无法触发按需加载逻辑。
可落地实现方案
方案一:自定义上下文重写变量查找逻辑(存量表达式无侵入)
这是最匹配懒加载需求的实现方式,核心是拦截SpEL的变量查找入口,只有当表达式实际引用某个具体变量时,才触发对应数据的加载,完全不需要预先加载全量大Map。
- 自定义EvaluationContext,重写
lookupVariable方法:public class LazyLoadEvaluationContext extends StandardEvaluationContext { private final Function<String, Object> lazyLoader; // 绑定当前上下文生命周期的加载缓存,避免同一次求值重复加载 private final Map<String, Object> loadedCache = new ConcurrentHashMap<>(); public LazyLoadEvaluationContext(Function<String, Object> lazyLoader) { this.lazyLoader = lazyLoader; } @Override public Object lookupVariable(String variableName) { // 优先处理SpEL内置标准变量(如#this、#root),避免覆盖原生逻辑 Object builtinVar = super.lookupVariable(variableName); if (builtinVar != null) { return builtinVar; } // 命中已加载缓存直接返回 if (loadedCache.containsKey(variableName)) { return loadedCache.get(variableName); } // 未命中则触发单条数据的按需加载 Object value = lazyLoader.apply(variableName); loadedCache.put(variableName, value); return value; } } - 替换原有上下文初始化逻辑,去掉全量Map预加载步骤:
// 直接传入懒加载逻辑,无需调用loader.loadBigMap() StandardEvaluationContext context = new LazyLoadEvaluationContext( name -> loader.loadForName(name) );
方案优势:
- 完全兼容原有SpEL表达式写法,存量表达式(如
#user.age > 18)不需要任何修改即可运行 - 仅在表达式实际引用某个变量时触发加载,彻底避免全量大Map的内存占用
- 内置缓存可避免同一次求值过程中对同一变量的重复加载,减少无效IO
方案二:显式注册懒加载函数(零自定义上下文)
如果不需要兼容存量表达式,可直接把懒加载逻辑注册为SpEL变量,通过显式函数调用实现懒加载:
- 注册函数到上下文:
StandardEvaluationContext context = new StandardEvaluationContext(); context.setVariable("load", (Function<String, Object>) name -> loader.loadForName(name)); - 对应SpEL表达式需要调整为显式调用函数的写法,例如原来的
#user.age > 18需要改为#load.apply('user').age > 18。
该方案侵入性较高,仅适合全新开发的表达式场景,不适合有存量表达式需要兼容的业务。
实现注意事项
- 不要为了兼容旧逻辑,在初始化上下文时调用
context.setVariables(bigMap),否则全量大Map会在求值开始前就被加载到内存,完全失去懒加载的意义 - 缓存必须和当前EvaluationContext的生命周期绑定,不要使用全局静态缓存,避免不同求值场景间的数据串扰和内存泄漏
- 如果单轮求值可能加载大量对象,可在缓存层增加容量上限或过期策略,避免加载过多变量反而造成内存占用过高
- 重写
lookupVariable时一定要优先返回父类的内置变量逻辑,否则会导致SpEL原生变量(如#this、#root)失效
内容的提问来源于stack exchange,提问作者Florian Huc
相关产品推荐
相关产品推荐

