Gradle 生態(tài)系統(tǒng)源碼分析

Gradle 進(jìn)階 第九篇

學(xué)不可以已

NamedDomainObjectContainer

這里一章會(huì)討論 Gradle 中一個(gè)非常重要的概念 NamedDomainObjectContainer。它是一個(gè)命名對(duì)象領(lǐng)域容器,源碼里的注釋解釋,NamedDomainObjectContainer 其實(shí)就是一個(gè)可以擁有創(chuàng)建單個(gè) item 能力的 NamedDomainObjectSet。而 NamedDomainObjectSet 又是可以根據(jù) item 名字來排列的一個(gè)集合,這個(gè)集合里的元素都是一個(gè)領(lǐng)域的對(duì)象,以不同的名字來區(qū)別,所以一個(gè)集合里的名字在一個(gè)領(lǐng)域里只能是惟一的。
我先展示一張類圖來讓大家直觀的了解一下 NamedDomainObject 相關(guān)的一系列關(guān)系:

NamedDomain.png

理解 NamedDomainObjectContainer 的字面意思比較容易,這里我就先用一個(gè)栗子來開始:

plugins {
    id 'java'
}

sourceSets {
  main {
    java {
      
    }
  }
  test {
    java{

    }
  }
  example {
    java{

    }
  }
}

上述栗子在 sourceSets 中創(chuàng)建了 main,test,example 三個(gè) sourceSet,實(shí)際上在 JavaPlugin 被 apply 的時(shí)候,已經(jīng)創(chuàng)建好了 main 和 test 兩個(gè) sourceSet,這里只是會(huì)去覆蓋這兩個(gè) sourceSet 的配置。如下:

 -- JavaPlugin.java
  private void configureSourceSets(JavaPluginConvention pluginConvention, final BuildOutputCleanupRegistry buildOutputCleanupRegistry) {
        Project project = pluginConvention.getProject();
        SourceSetContainer sourceSets = pluginConvention.getSourceSets();

        SourceSet main = sourceSets.create(SourceSet.MAIN_SOURCE_SET_NAME);

        SourceSet test = sourceSets.create(SourceSet.TEST_SOURCE_SET_NAME);
        ...
    }

但是 example 是我們通過名字來創(chuàng)建了一個(gè)新的 sourceSet 對(duì)象。sourceSet 是邏輯上相關(guān)的一組 Java 代碼以及資源文件的集合。這里就不繼續(xù)討論,主要借這個(gè)栗子來深入了解一下 NamedDomainObjectContainer。

sourceSets 的函數(shù)調(diào)用最終代理到了 SourceSetContainer 接口,它的實(shí)現(xiàn)類是 DefaultSourceSetContainer:

public class DefaultSourceSetContainer extends AbstractValidatingNamedDomainObjectContainer<SourceSet> implements SourceSetContainer{
    ...
     protected SourceSet doCreate(String name) {
        DefaultSourceSet sourceSet = instantiator.newInstance(DefaultSourceSet.class, name, objectFactory);
        sourceSet.setClasses(instantiator.newInstance(DefaultSourceSetOutput.class, sourceSet.getDisplayName(), fileResolver, fileCollectionFactory));
        return sourceSet;
    }
    ...

}

這里的 doCreate 就是就是通過傳入一個(gè) name 來創(chuàng)建一個(gè) SourceSet 的實(shí)例。

sourceSet 創(chuàng)建細(xì)節(jié)

現(xiàn)在我們深入分析一下源碼,看看 sourceSets 代碼塊中的代碼是如何創(chuàng)建的 main sourceSet。

sourceSets{
    main{
        java{

        }
    }
  ...
}

在第二章里講過關(guān)于 Extension 先關(guān)的概念,這里就直接跳過,在 apply javaPlugin 之后,會(huì)被動(dòng)的 apply JvmEcosystemPlugin,在JvmEcosystemPlugin 中有:

        p.getExtensions().add(SourceSetContainer.class, "sourceSets", sourceSets);

我們看到 SourceSetContainer 以 "sourceSets" 這個(gè)名字被加入了項(xiàng)目的約定群中。因此 sourceSets{} //surceSets(Closure closure)// 函數(shù)調(diào)用會(huì)通過 DefaultConvent 的 configureExtension 函數(shù)調(diào)用到 ExtensionsStorage 的 configureExtension 方法中來。這個(gè)細(xì)節(jié)在系列文章的第三篇中有講。

    private Object configureExtension(String name, Object[] args) {
        Closure closure = (Closure) args[0];
        Action<Object> action = ConfigureUtil.configureUsing(closure);
        return extensionsStorage.configureExtension(name, action);
    }
 public <T> T configureExtension(String name, Action<? super T> action) {
        ExtensionHolder<T> extensionHolder = uncheckedCast(extensions.get(name));
        if (extensionHolder != null) {
            return extensionHolder.configure(action);
        }
        throw unknownExtensionException(name);
    }

這里截一張圖來印證:

SourceSetCatch.PNG

接下來有一點(diǎn)需要強(qiáng)調(diào),在 Gradle 的 DSL 世界中,一個(gè)函數(shù)調(diào)用如果參數(shù)是閉包,一般都會(huì)調(diào)用 ConfigureUtil 這個(gè)類來進(jìn)行配置。其中有一個(gè)非常重要的類:

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

        if (target instanceof Configurable) {
            ((Configurable) target).configure(configureClosure);
        } else {
            configureTarget(configureClosure, target, new ConfigureDelegate(configureClosure, target));
        }

        return target;
    }

這里會(huì)判斷如果傳入的 target 即需要配置的類是可以配置的,就直接調(diào)用該類的 configure 方法,如果這個(gè) target 不是可配置的,那就會(huì)給這個(gè) target 創(chuàng)建一個(gè)配置代理來配置。
在這里 SourceSetContainer 的實(shí)現(xiàn)類 DefaultSourceSetContainer 繼承自
AbstractNamedDomainObjectContainer 是可配置的,所以調(diào)用該類的 configure 函數(shù)。

    @Override
    public AbstractNamedDomainObjectContainer<T> configure(Closure configureClosure) {
        ConfigureDelegate delegate = createConfigureDelegate(configureClosure);
        ConfigureUtil.configureSelf(configureClosure, this, delegate);
        return this;
    }

在這個(gè)函數(shù)中首先調(diào)用 createConfigureDelegate 來創(chuàng)建配置代理。然后通過調(diào)用 ConfigureUtil 的 configureSelf 函數(shù),傳入閉包(即配置的參數(shù)),以及配置代理來進(jìn)行配置。會(huì)調(diào)用下面這個(gè)方法:

class ConfigureDelegate extends GroovyObjectSupport {
  ...
  @Override
    public Object invokeMethod(String name, Object paramsObj) {
       ...
       
          result = _configure(name, params);
       ...        
    }
}

這里的 ConfigureDelegate 實(shí)際是 NamedDomainObjectContainerConfigureDelegate:

class NamedDomainObjectContainerConfigureDelegate extends ConfigureDelegate {
    ...
    @Override
    protected DynamicInvokeResult _configure(String name, Object[] params) {
        if (params.length == 1 && params[0] instanceof Closure) {
            return DynamicInvokeResult.found(_container.create(name, (Closure) params[0]));
        }
        return DynamicInvokeResult.notFound();
    }
}

其中有一點(diǎn)重要的是 _configure 中實(shí)際調(diào)用 _container.create(name, (Closure) params[0]),調(diào)用了 DefaultSourceSetsContainer 基類的 create 方法。

abstract class AbstractNamedDomainObjectContainer<T> extends DefaultNamedDomainObjectSet<T> implements NamedDomainObjectContainer<T>, HasPublicType {
    ...
    @Override
    public T create(String name, Action<? super T> configureAction) throws InvalidUserDataException {
        assertMutable("create(String, Action)");
        assertCanAdd(name);
        T object = doCreate(name);
        add(object);
        configureAction.execute(object);
        return object;
    }
    ...
}

最終調(diào)用到 DefaultSourceSetsContainer 的 doCreate 方法。

    @Override
    protected SourceSet doCreate(String name) {
        DefaultSourceSet sourceSet = instantiator.newInstance(DefaultSourceSet.class, name, objectFactory);
        sourceSet.setClasses(instantiator.newInstance(DefaultSourceSetOutput.class, sourceSet.getDisplayName(), fileResolver, fileCollectionFactory));
        return sourceSet;
    }

通過這個(gè)栗子的解析,我們就能看到 NamedDomainObjectContainer 是如何通過閉包來創(chuàng)建并配置其內(nèi)部的單個(gè)項(xiàng)的。

NameDomainObjectContainer 總結(jié)

總的流程就是:

  1. 先找到對(duì)應(yīng)的 NamedDomainObjectContainer, 就像 SourceSets 的栗子中, SourceSetsContainer 是在 apply 相關(guān)的 plugin 的時(shí)候,通過添加 extension 的方式,把腳本里的 SourceSets{} 映射到 SourceSetsContainer。
  2. 然后創(chuàng)建相應(yīng)的 ConfigureDelegate 即 NamedDomainObjectContainerConfigureDelegate 。
  3. 然后通過 ConfigureUtil 類的 configure 方法通過傳入的閉包以及名字來創(chuàng)建并配置一個(gè)領(lǐng)域相關(guān)的項(xiàng)。就像 SourceSets 的栗子 SourceSets{ main{}},創(chuàng)建了一個(gè)名字為 main 的 SourceSet。

提到 ConfigureUtil, 這里就把 ConfigureUtil 解析一下:

  class ConfigureUtil{
    public static <T> T configureByMap(Map<?,?> properties,T delegate){...}

    public static <T> T configureByMap(Map<?, ?> properties, T delegate, Collection<?> mandatoryKeys) {...}

    public static <T> T configure(@Nullable Closure configureClosure, T target) {...}

    public static <T> Action<T> configureUsing(@Nullable final Closure configureClosure) {...}

    public static <T> T configureSelf(@Nullable Closure configureClosure, T target) {...}

    public static <T> T configureSelf(@Nullable Closure configureClosure, T target, ConfigureDelegate closureDelegate) {...}

    private static <T> void configureTarget(Closure configureClosure, T target, ConfigureDelegate closureDelegate) {...}

  }
   

其中 configureByMap 方法調(diào)用的栗子是:apply plugin:"java-library" 或者 fileTree(dir: 'libs', include: ['.jar', '.aar'])。

而 configure configureSelf configureUsing 這三個(gè)函數(shù),最終都會(huì)調(diào)用 configureTarget 來作真正的 configure:

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);
    }

我在這里畫了一幅類圖:

ConfigurUtil.png

里面有兩個(gè)重要的 Action : ClosureBackedAction 和 WrappedConfigureAction。

ClosureBackedAction

public class ClosureBackedAction<T> implements Action<T> {
...

    public static <T> void execute(T delegate, Closure<?> closure) {
        new ClosureBackedAction<T>(closure).execute(delegate);
    }

    @Override
    public void execute(T delegate) {
        if (closure == null) {
            return;
        }

        try {
            if (configurableAware && delegate instanceof Configurable) {
                ((Configurable) delegate).configure(closure);
            } else {
                Closure copy = (Closure) closure.clone();
                copy.setResolveStrategy(resolveStrategy);
                copy.setDelegate(delegate);
                if (copy.getMaximumNumberOfParameters() == 0) {
                    copy.call();
                } else {
                    copy.call(delegate);
                }
            }
        } catch (groovy.lang.MissingMethodException e) {
            if (Objects.equal(e.getType(), closure.getClass()) && Objects.equal(e.getMethod(), "doCall")) {
                throw new InvalidActionClosureException(closure, delegate);
            }
            throw e;
        }
    }
  ...
}

WrappedConfigureAction

public static class WrappedConfigureAction<T> implements Action<T> {
        private final Closure configureClosure;

        WrappedConfigureAction(Closure configureClosure) {
            this.configureClosure = configureClosure;
        }

        @Override
        public void execute(T t) {
            configure(configureClosure, t);
        }

        public Closure getConfigureClosure() {
            return configureClosure;
        }
    }

其中 ClosureBackedAction 的 execute 方法里判斷如果 delegate 是可配置的,就會(huì)直接調(diào)用 configure 方法并且把閉包以參數(shù)形式傳入。否則將會(huì)把 delegate 設(shè)置成為閉包的代理,然后調(diào)用閉包的 call 方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容