Java调用Groovy:如何实现隐式根对象绑定的高性能简洁语法?
已知$ROOT$的具体类型时,完全可以避开with{}的性能损耗,通过以下几种方式实现无前缀的简洁语法,同时保持和直接调用$ROOT$.empty相近的性能:
1. 静态编译的自定义脚本基类
利用Groovy的静态编译特性,提前定义一个包含$ROOT$类型属性的脚本基类,让脚本直接继承该类并编译为静态字节码,这样成员访问会在编译时就绑定到目标对象,完全消除动态调用开销。
代码示例
首先定义静态编译的脚本基类:
@groovy.transform.CompileStatic abstract class RootScript extends groovy.lang.Script { // 替换为你实际的$ROOT$类型,比如Collection Collection root }
然后在Java中使用这个基类解析脚本:
// 脚本内容直接写简洁语法 String snippet = "empty == true"; // 配置编译选项,指定脚本基类 CompileConfiguration config = new CompileConfiguration(); config.setScriptBaseClass(RootScript.class.getName()); // 创建Shell并编译脚本 GroovyShell shell = new GroovyShell(config); Script script = shell.parse(snippet); // 设置root对象 ((RootScript) script).setRoot(Collections.emptyList()); // 执行脚本 script.run();
2. 自定义Binding实现成员映射
通过重写Binding的getVariable方法,将脚本中访问的无前缀成员直接映射到$ROOT$对象的对应方法/属性。因为已知$ROOT$的类型,可以提前缓存反射方法,避免每次调用的反射开销。
代码示例
class RootBinding extends Binding { private final Object root; // 缓存方法,避免重复反射 private final Method isEmptyMethod; public RootBinding(Object root) throws NoSuchMethodException { this.root = root; // 提前获取目标方法,这里对应Collection.isEmpty() this.isEmptyMethod = root.getClass().getMethod("isEmpty"); } @Override public Object getVariable(String name) throws MissingPropertyException { // 映射脚本中的"empty"到root.isEmpty() if ("empty".equals(name)) { try { return isEmptyMethod.invoke(root); } catch (Exception e) { throw new RuntimeException(e); } } // 其他变量按默认逻辑处理 return super.getVariable(name); } }
使用这个自定义Binding:
String snippet = "empty == true"; Collection root = Collections.emptyList(); RootBinding binding = new RootBinding(root); Expression e = new GroovyShell().parse(snippet, binding); e.run();
3. 编译时元编程(AST转换)
如果需要更通用的解决方案,可以编写Groovy AST转换,在编译脚本时自动将无前缀的成员访问替换为$ROOT$.成员的形式。这种方式完全在编译时处理,运行时没有额外开销。
核心思路
自定义AST转换器,遍历脚本的语法树,将所有无前缀的属性访问(比如empty)替换为$ROOT$.empty的表达式,然后开启静态编译,确保编译后的字节码和直接编写前缀调用完全一致。
简化示例(关键逻辑)
@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS) class RootMemberTransform extends AbstractASTTransformation { @Override void visit(ASTNode[] nodes, SourceUnit sourceUnit) { sourceUnit.AST.statements.each { stmt -> if (stmt instanceof ExpressionStatement) { Expression expr = stmt.expression; // 识别无前缀的属性访问表达式 if (expr instanceof BinaryExpression && expr.left instanceof PropertyExpression) { PropertyExpression propExpr = (PropertyExpression) expr.left; if (propExpr.objectExpression instanceof VariableExpression && propExpr.objectExpression.name == "this") { // 替换为$ROOT$.成员的形式 propExpr.objectExpression = new VariableExpression("$ROOT$"); } } } } } }
使用时需要将AST转换注册到编译配置中,脚本中就可以直接写empty == true,编译时会自动替换为$ROOT$.empty == true。
性能说明
以上三种方案都是在编译时完成成员绑定,避免了with{}闭包带来的动态上下文切换和多次方法调用开销,JMH测试下性能会和直接编写$ROOT$.empty == true基本一致,不会出现10倍的性能差距。
内容的提问来源于stack exchange,提问作者drekbour

