没有 resolveStrategy=DELEGATE_FIRST 属性最终成为绑定变量

without resolveStrategy=DELEGATE_FIRST properties end up as binding variables

我正在尝试编写可以从 Groovy 配置文件加载的 java bean。配置格式需要闭包中的属性,如果我不调用 c.setResolveStrategy(Closure.DELEGATE_FIRST),那么闭包内设置的所有属性最终都会作为绑定变量。我的程序输出:

In closure
confpojo.myTestProp: null
binding.myTestProp: true
confpojo.app.myOther.myTestSubProp: null
binding.myTestSubProp: true

在这个答案中 他们没有更改默认的 resolveStrategy,它似乎有效。有什么不同? configaaa.groovy:

    app {
        println 'In closure'
    
        myTestProp = true
    
        myOther {
            myTestSubProp = true
        }
    }

_

public abstract class AaaTestGroovyConfig extends Script {

    public static class App {

        public void myOther(final Closure c) {
            c.setDelegate(myOther);
            // c.setResolveStrategy(Closure.DELEGATE_FIRST);
            c.call();
        }

        private Boolean myTestProp;

        private final Other myOther = new Other();

        public Boolean getMyTestProp() {
            return myTestProp;
        }

        public void setMyTestProp(final Boolean active) {
            this.myTestProp = active;
        }

    }

    public void app(final Closure c) {
        c.setDelegate(app);
        // c.setResolveStrategy(Closure.DELEGATE_FIRST);
        c.call();
    }

    private App app = new App();
    public static void main(final String[] args) throws Exception {
        final CompilerConfiguration cc = new CompilerConfiguration();
        cc.setScriptBaseClass(AaaTestGroovyConfig.class.getName());
        // final ClassLoader cl = AaaTestGroovyConfig.class.getClassLoader();
        final Binding binding = new Binding();
        final GroovyShell shell = new GroovyShell(binding, cc);
        final Script script = shell.parse(new File("configaaa.groovy"));
        final AaaTestGroovyConfig confpojo = (AaaTestGroovyConfig) script;
        // ((DelegatingScript) script).setDelegate(confpojo);
        script.run();

        System.out.println("confpojo.myTestProp: " + confpojo.app.myTestProp);
        printBindingVar(binding, "myTestProp");

        System.out
            .println("confpojo.app.myOther.myTestSubProp: " + confpojo.app.myOther.myTestSubProp);
        printBindingVar(binding, "myTestSubProp");

    }

    private static void printBindingVar(final Binding binding, final String name) {
        System.out
            .println(
                "binding." + name + ": " + (binding.hasVariable(name)
                    ? binding.getVariable(name)
                    : ""));
    }

    public static class Other {

        private Boolean myTestSubProp;

        public Boolean getMyTestSubProp() {
            return myTestSubProp;
        }

        public void setMyTestSubProp(final Boolean myTestSubProp) {
            this.myTestSubProp = myTestSubProp;
        }
    }

    public App getApp() {
        return app;
    }

    public void setApp(final App app) {
        this.app = app;
    }


}

因为默认值为OWNER_FIRST

https://docs.groovy-lang.org/latest/html/api/groovy/lang/Closure.html#OWNER_FIRST

并且您有 2 级关闭,所以 - 所有者对他们来说是不同的

尝试这样的事情,你会看到不同

app {
    println "delegate=$delegate owner=${owner.getClass()}"
    myOther {
        println "delegate=$delegate owner=${owner.getClass()}"
    }
}

PS:让我建议你让你的代码更优雅:

//generic config class
class MyConf {
    private HashMap objMap
    static def build(HashMap<String,Class> classMap, Closure builder){
        MyConf cfg = new MyConf()
        cfg.objMap = classMap.collectEntries{ k,cl-> [k, cl.newInstance()] }
        cfg.objMap.each{ k,obj->
            //define method with name `k` and with optional closure parameter
            cfg.metaClass[k] = {Closure c=null ->
                if(c) {
                    // call init closure with preset delegate and owner
                    return c.rehydrate(/*delegate*/ obj, /*owner*/cfg, /*this*/cfg).call() 
                }
                return obj //return object itself if no closure 
            }
        }
        cfg.with(builder) // call root builder closure with cfg as a delegate
        return cfg
    }
}

//bean 1
@groovy.transform.ToString
class A{
    int id
    String name
}

//bean 2
@groovy.transform.ToString
class B{
    int id
    String txt
}

//beans init
def cfg = MyConf.build(app:A.class, other:B.class){
    app {
        id = 123
        name = "hello 123"
        other {
            id = 456
            txt = "bye 456"
        }
    }
}

//get initialized beans
println cfg.app()
println cfg.other()