在将闭包传递给 gradle 扩展时,为什么闭包的所有者不是主要的 Projects 对象?

On passing a closure to a gradle extension, why the owner of closure is not the main Projects object?

我正在查看闭包作用域并发现输出计数器很直观,这段代码在 build.gradle 文件中

plugins {
    id 'java'
}

sourceSets {

    println 'source sets closure scopes this,owner, delegate'
    println this.toString() // output: root project 'multigradle'
    println owner.toString() // output: DynamicObject for SourceSet container
    println delegate.toString() // output: SourceSet container
    
}

为什么owner不等于this,gradle是否克隆闭包?

PS : 对于将来阅读它的任何人 'multigradle' 是我的 gradle 项目名称。

TL;DR;

基本上在 gradle 源代码中有一个类似这样的方法:

 public Object sourceSets(Closure closure) {
   // do stuff like configuring the closure
   closure.call()
 }

所以当你打电话时:

sourceSets { 
  some code
}

(顺便说一句,这与调用 sourceSets({ some code }) 相同,只是删除了括号,这在 groovy 中是可以的)

调用sourceSets 方法时,“某些代码”不会立即执行。 Gradle 可以选择在他们决定的时间执行它。具体来说,他们可以(并且确实)在实际执行闭包之前配置所有者和委托之类的东西。

更长的版本

原来 build.gradle 文件中的 sourceSets 方法实际上是由插件添加的,例如 java/kotlin/groovy 插件。

作为示例,我们可以查看 java 插件和具有以下代码的 DefaultJavaPluginConvention class

    private final SourceSetContainer sourceSets;

    @Override
    public Object sourceSets(Closure closure) {
        return sourceSets.configure(closure);
    }

这是当您在 build.gradle 文件中键入 sourceSets { ... } 时调用的方法。它获得闭包并继续将其交给源集容器的 configure 方法。请注意,我们还没有执行闭包,我们只是将其作为未执行的代码块传递。

如果我们稍微挖掘一下,我们会在 AbstractNamedDomainObjectContainer class:

中找到 configure 方法
    public AbstractNamedDomainObjectContainer<T> configure(Closure configureClosure) {
        ConfigureDelegate delegate = createConfigureDelegate(configureClosure);
        ConfigureUtil.configureSelf(configureClosure, this, delegate);
        return this;
    }

SourceSetContainer 是一个接口,实现 class 继承自 AbstractNamedDomainObjectContainer...这是正确的 configure 方法)

其中 ConfigureUtil 具有以下代码:

    public static <T> T configureSelf(@Nullable Closure configureClosure, T target, ConfigureDelegate closureDelegate) {
        if (configureClosure == null) {
            return target;
        }

        configureTarget(configureClosure, target, closureDelegate);
        return target;
    }

    private static <T> void configureTarget(Closure configureClosure, T target, ConfigureDelegate closureDelegate) {
        if (!(configureClosure instanceof GeneratedClosure)) {
            new ClosureBackedAction<T>(configureClosure, Closure.DELEGATE_FIRST, false).execute(target);
            return;
        }

        // Hackery to make closure execution faster, by short-circuiting the expensive property and method lookup on Closure
        Closure withNewOwner = configureClosure.rehydrate(target, closureDelegate, configureClosure.getThisObject());
        new ClosureBackedAction<T>(withNewOwner, Closure.OWNER_ONLY, false).execute(target);
    }

其中相关部​​分是对 groovy Closure rehydrate 方法的调用,根据文档,该方法执行以下操作:

Returns a copy of this closure for which the delegate, owner and thisObject are replaced with the supplied parameters. Use this when you want to rehydrate a closure which has been made serializable thanks to the dehydrate() method.

仅在 configureTarget 方法的最后一行 gradle 调用 execute 对创建的表示闭包的操作。所以闭包的执行是在owner、delegate和this指针根据gradle需要配置完成后执行的。