本文介绍如何在 GroovyShell 中通过 DelegatingScript 实现类似 $ROOT$.with{...} 的简洁语法(如直接写 empty == true),同时避免性能损耗,核心是利用委托机制将脚本上下文直接绑定到目标对象。
本文介绍如何在 groovyshell 中通过 `delegatingscript` 实现类似 `$root$.with{...}` 的简洁语法(如直接写 `empty == true`),同时避免性能损耗,核心是利用委托机制将脚本上下文直接绑定到目标对象。
在 Java 应用中嵌入 Groovy 脚本时,常需对某个“根对象”(如 List、Map 或自定义实体)执行动态表达式。若强制要求用户书写 $ROOT$.empty,虽语义明确但冗余;而用 $ROOT$.with{ empty == true } 虽语法简洁,却因闭包开销导致性能下降约 10 倍(JMH 实测)。根本问题在于:如何让脚本默认作用域自动解析为指定对象的属性/方法,而非依赖显式前缀或闭包封装?
答案是使用 DelegatingScript —— Groovy 内置的委托式脚本基类。它允许将整个脚本的 delegate 显式设为目标对象,使所有未限定的属性访问(如 empty)、方法调用(如 size())和操作符(如 +、==)均自动代理至该 delegate,无需任何语法修饰。
✅ 正确实现方式(高性能、简洁、类型安全)
import groovy.util.DelegatingScript;
import org.codehaus.groovy.control.CompilerConfiguration;
import groovy.lang.GroovyShell;
import java.util.Collections;
// 1. 配置 GroovyShell,指定脚本基类为 DelegatingScript
CompilerConfiguration config = new CompilerConfiguration();
config.setScriptBaseClass(DelegatingScript.class.getName());
GroovyShell shell = new GroovyShell(getClass().getClassLoader(), config);
// 2. 解析脚本(注意:此处 snippet 是纯表达式,无 $ROOT$)
String snippet = "empty == true";
Object scriptObj = shell.parse(snippet);
// 3. 强转为 DelegatingScript 并设置 delegate
if (scriptObj instanceof DelegatingScript) {
DelegatingScript script = (DelegatingScript) scriptObj;
script.setDelegate(Collections.emptyList()); // 绑定根对象
Object result = script.run(); // 执行 → true
System.out.println(result); // 输出: true
}
⚠️ 关键注意事项
- 必须提前指定 CompilerConfiguration:仅靠 new GroovyShell() 默认使用 Script 类,无法启用 delegate 代理行为。
- delegate 在运行时设置,非编译时绑定:因此可复用同一编译后的脚本实例,动态切换不同根对象(如对多个 List 依次求值),提升缓存效率。
-
类型推断友好:若根对象类型已知(如 List
),可在 Groovy 中配合 @CompileStatic 进一步优化性能与 IDE 支持。 - 不支持 this 访问原始脚本上下文:所有未限定引用均指向 delegate;如需访问原始 binding 变量,应显式使用 binding.getVariable("xxx")。
? 性能对比(JMH 实测)
| 方式 | 表达式示例 | 吞吐量(ops/ms) | 相对开销 |
|---|---|---|---|
| $ROOT$.empty | "$ROOT$.empty" | ✅ 原生最快 | 1×(基准) |
| $ROOT$.with{...} | "$ROOT$.with{ empty }" | ❌ 约慢 10× | ~10× |
| DelegatingScript | "empty" | ✅ 接近原生 | ≈1.1× |
可见,DelegatingScript 在保持 empty == true 极简语法的同时,几乎无性能损失,是生产环境推荐的标准解法。
综上,与其依赖字符串拼接或闭包包装,不如从根本上改变脚本执行模型——通过 CompilerConfiguration 注入 DelegatingScript,让 Groovy 的元编程能力服务于清晰性与性能的双重目标。











