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)系:

理解 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);
}
這里截一張圖來印證:
接下來有一點(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é)
總的流程就是:
- 先找到對(duì)應(yīng)的 NamedDomainObjectContainer, 就像 SourceSets 的栗子中, SourceSetsContainer 是在 apply 相關(guān)的 plugin 的時(shí)候,通過添加 extension 的方式,把腳本里的 SourceSets{} 映射到 SourceSetsContainer。
- 然后創(chuàng)建相應(yīng)的 ConfigureDelegate 即 NamedDomainObjectContainerConfigureDelegate 。
- 然后通過 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);
}
我在這里畫了一幅類圖:

里面有兩個(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 方法。