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

Gradle 入門 第二篇

精誠所至碧聪,金石為開厦章。

Gradle 腳本的函數(shù)的調(diào)用

接著上一篇文章的尾巴雀费,現(xiàn)在需要在詳細(xì)一點(diǎn)考慮函數(shù)的調(diào)用比肄。這里所說的函數(shù)調(diào)用是指 '.gradle' 里的那些函數(shù)調(diào)用是怎么在Gradle 中映射到方法的實(shí)現(xiàn)男翰。

其中最基礎(chǔ)的一個(gè)接口是 DynamicObject 接口另患,它位于Gradle源碼里的 core-api 模塊,代碼如下:

public interface DynamicObject extends MethodAccess, PropertyAccess {
    /**
     * Creates a {@link MissingPropertyException} for getting an unknown property of this object.
     */
    MissingPropertyException getMissingProperty(String name);

    /**
     * Creates a {@link MissingPropertyException} for setting an unknown property of this object.
     */
    MissingPropertyException setMissingProperty(String name);

    /**
     * Creates a {@link MissingMethodException} for invoking an unknown method on this object.
     */
    MissingMethodException methodMissingException(String name, Object... params);

    /**
     * Don't use this method. Use the overload {@link #tryGetProperty(String)} instead.
     */
    Object getProperty(String name) throws MissingPropertyException;

    /**
     * Don't use this method. Use the overload {@link #trySetProperty(String, Object)} instead.
     */
    void setProperty(String name, Object value) throws MissingPropertyException;

    /**
     * Don't use this method. Use the overload {@link MethodAccess#tryInvokeMethod(String, Object...)} instead.
     */
    Object invokeMethod(String name, Object... arguments) throws MissingMethodException;
}

接口的一個(gè)抽象實(shí)現(xiàn)是 AbstractDynamicObject奏篙,它繼承自DynamicObject柴淘,是 Gradle 腳本函數(shù)調(diào)用的關(guān)鍵。畫個(gè)簡單的圖來表示一下:

GradleMethodSeek.png

圖2-1 流程圖

從圖上可以看出秘通, Gradle 掃描
setting.gradle 文件并會識別整個(gè)項(xiàng)目的所有 project (root project and subproject) 为严,同時(shí)會在 Gradle runtime 中創(chuàng)建對應(yīng)的DefaultProject 對象,然后加載這些 project 對應(yīng)的 Gradle 腳本文件(也就是各個(gè)項(xiàng)目目錄中的 build.gradle 文件)肺稀,接著把它們編譯成 class(XXScript) 文件第股,如上圖所示,Gradle runtime 會調(diào)用 XXScript.run() 方法開始解析并執(zhí)行腳本话原。在這里有一步操作是比較關(guān)鍵夕吻,并且在 run 方法之前運(yùn)行诲锹,就是把 DefaultProject 傳給 XXScript。而這一步操作的作用涉馅,會在后面 DynamicInvoke System 里講解归园,先埋個(gè)伏筆。

我們這里說的 xxScript 就是前文中所提到的 "build_xxxx extends ProjectScript"稚矿, 忘記的同學(xué)可以回顧一下上一篇文章庸诱。

Gradle 腳本的執(zhí)行是利用了 Groovy 的 RuntimeMateProgramma 機(jī)制,它會把方法調(diào)用最終映射到特定的類的 invokeMethod 方法上晤揣,這里的 invokeMethod 在xxScript 的基類 BaseScript 中桥爽。如圖2-1 所示接下來就會通過 invokeMethod 方法的調(diào)用進(jìn)入 DynamicInvoke System 世界的大門。

動態(tài)調(diào)用系統(tǒng)

先把這個(gè)系統(tǒng)大概的類圖勾勒一下昧识,其中重點(diǎn)關(guān)注一下圖2-1中所示的 ScriptDynamicObject钠四、BeanDynamicObject、以及 ExtensibleDynamicObject跪楞。

DynamicInvoke.png

圖2-2 類圖

上面提到過的 invokeMethod 方法的具體實(shí)現(xiàn)如下:

public abstract class BasicScript extends Script implements .. {
    private ScriptDynamicObject dynamicObject = new ScriptDynamicObject(this);
    ...
    public Object invokeMethod(String name, Object args) {
        return dynamicObject.invokeMethod(name, (Object[]) args);
    }
}

其中參數(shù) name 是真正想要調(diào)用的方法名稱缀去,參數(shù) args 是方法對應(yīng)的參數(shù)。這里我們可以舉個(gè)栗子:

 apply plugin:"java"
 // apply 是函數(shù)名字习霹, 參數(shù)是一個(gè) map 類型朵耕,key 是 plugin , Value 是"java".

dynamicObject 的類型是 ScriptDynamicObject 淋叶,所以這里的 invokeMethod 方法也就是 ScriptDynamicObject 的 invokeMethod 方法阎曹,而 ScriptDynamicObject 的 invokeMethod 方法實(shí)現(xiàn)在基類
AbstractDynamicObject, 如下所示:

AbstractDynamicObject

public Object invokeMethod(String name, Object... arguments) ..{
        DynamicInvokeResult result = tryInvokeMethod(name, arguments);
       ...
    }

接著 invokeMethod 又繼續(xù)調(diào)用了 ScriptDynamicObject 的 tryInvokeMethod ,如下所示:

public DynamicInvokeResult tryInvokeMethod(String name, Object... arguments) {
            DynamicInvokeResult result = scriptObject.tryInvokeMethod(name, arguments);
            if (result.isFound()) {
                return result;
            }
            return dynamicTarget.tryInvokeMethod(name, arguments);
        }

ScriptDynamicObject 顧名思義是將 XXScript invokeMethod 方法動態(tài)轉(zhuǎn)移到自身的一個(gè)類煞檩。我們可以看到在其內(nèi)部又有兩個(gè)動態(tài)代理對象处嫌,其中 scriptObject 的類型是 BeanDynamicObject,而 另外的一個(gè)dynamicTarget 就是上面提到的 DefaultProject 里的 ExtensibleDynamicObject 對象斟湃。從 tryInvokeMethod 方法中可以看到熏迹,先通過 BeanDynamicObject 類型的代理去找方法調(diào)用(也就是找 apply 真正實(shí)現(xiàn)的地方),如果沒有找到凝赛,就會去在 dynamicTarget 中尋找方法調(diào)用注暗。

private static final class ScriptDynamicObject extends AbstractDynamicObject {

        ...
        private final DynamicObject scriptObject;
        private DynamicObject dynamicTarget;

        ScriptDynamicObject(BasicScript script) {
            ...
            scriptObject = new BeanDynamicObject(script).withNotImplementsMissing();
            dynamicTarget = scriptObject;
        }

        public void setTarget(Object target) {
            dynamicTarget = DynamicObjectUtil.asDynamicObject(target);
        }
        ...
}

這里簡單的描述一下 BeanDynamicObject 的作用是使用常規(guī)反射來提供對bean的屬性和方法的訪問,這句解釋是摘自 BeanDynamicObject 的類文檔墓猎,言簡意賅捆昏,在這里就是把 XXScript 包裝了一下。 所以用上面的舉的栗子毙沾,通過反射嘗試訪問 XXScript 里的 apply 方法骗卜, 在XXScript 類的基類 ProjectScript 里剛好擁有 apply 方法。所以栗子里的方法找到了實(shí)現(xiàn)的地方。

public abstract class ProjectScript extends PluginsAwareScript {

    public void apply(Closure closure) {
        getScriptTarget().apply(closure);
    }

    ...
    public void apply(Map options) {
        getScriptTarget().apply(options);
    }
    ...
}

我們再舉一個(gè)栗子:dependencies 的方法查找寇仓。

apply plugin:"java"

 dependencies {
       ...
 }

根據(jù)我們上面所得的結(jié)論举户,首先要在 XXScript 以及它的基類里面查找,但是并沒有發(fā)現(xiàn) dependencies 這個(gè)方法遍烦,所以 Gradle 會轉(zhuǎn)到 dynamicTarget(ExtensibleDynamicObject) 中尋找俭嘁。

這里需要停一下,分析一下 ExtensibleDynamicObject 這個(gè)類乳愉,在這個(gè)類中有一段函數(shù):

 private void updateDelegates() {
        DynamicObject[] delegates = new DynamicObject[6];
        delegates[0] = dynamicDelegate;
        delegates[1] = extraPropertiesDynamicObject;
        int idx = 2;
        if (beforeConvention != null) {
            delegates[idx++] = beforeConvention;
        }
        if (convention != null) {
            delegates[idx++] = convention.getExtensionsAsDynamicObject();
        }
        if (afterConvention != null) {
            delegates[idx++] = afterConvention;
        }
        boolean addedParent = false;
        if (parent != null) {
            addedParent = true;
            delegates[idx++] = parent;
        }
        DynamicObject[] objects = new DynamicObject[idx];
        System.arraycopy(delegates, 0, objects, 0, idx);
        setObjects(objects);

        if (addedParent) {
            idx--;
            objects = new DynamicObject[idx];
            System.arraycopy(delegates, 0, objects, 0, idx);
            setObjectsForUpdate(objects);
        }
    }

DynamicObject[] delegates = new DynamicObject[6]; 意思是指 ExtensibleDynamicObject 有6個(gè)方法的 Delegate兄淫,查找方法的調(diào)用最終會按順序在這6個(gè) Delegate 中去尋找屯远。在這里我先引用一下官網(wǎng)的上的一個(gè)小節(jié):
https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#N15050

A project has 5 method 'scopes', which it searches for methods:

1. The Project object itself.
2. The build file. The project searches for a matching method declared in the build file.
3. The extensions added to the project by the plugins. Each extension is available as a method which takes a closure or Action as a parameter.
The convention methods added to the project by the plugins. A plugin can add properties and method to a project through the project's Convention object.
4. The tasks of the project. A method is added for each task, using the name of the task as the method name and taking a single closure or Action parameter. The method calls the Task.configure(groovy.lang.Closure) method for the associated task with the provided closure. For example, if the project has a task called compile, then a method is added with the following signature: void compile(Closure configureClosure).
5. The methods of the parent project, recursively up to the root project.
A property of the project whose value is a closure. The closure is treated as a method and called with the provided parameters. The property is located as described above.

這里我覺得官網(wǎng)上的文檔有點(diǎn)欠妥蔓姚,從我們上面的分析,The build file. The project searches for a matching method declared in the build file. 應(yīng)該是在 The Project object itself 之前慨丐。

轉(zhuǎn)回話題坡脐,我們來觀察一下 ExtensibleDynamicObject 的構(gòu)造函數(shù):

public ExtensibleDynamicObject(Object delegate, Class<?> publicType, InstanceGenerator instanceGenerator) {
        this(delegate, createDynamicObject(delegate, publicType), new DefaultConvention(instanceGenerator));
    }

    ...
    public ExtensibleDynamicObject(Object delegate, AbstractDynamicObject dynamicDelegate, Convention convention) {
        this.dynamicDelegate = dynamicDelegate;
        this.convention = convention;
        this.extraPropertiesDynamicObject = new ExtraPropertiesDynamicObjectAdapter(delegate.getClass(), convention.getExtraProperties());

        updateDelegates();
    }

     private static BeanDynamicObject createDynamicObject(Object delegate, Class<?> publicType) {
        return new BeanDynamicObject(delegate, publicType);
    }

同時(shí)還有 new 它的地方:

 class DefaultProject ...{

             extensibleDynamicObject = new ExtensibleDynamicObject(this, Project.class, services.get(InstantiatorFactory.class).decorateLenient(services));

 }

綜上所得,dynamicDelegate 就是 BeanDynamicObject 把 DefaultProject 包了一層房揭,結(jié)合上面提到過的內(nèi)容备闲,dependencies 方法是需要在 DefaultProject 中尋找,結(jié)果如下:

 class DefaultProject ...{
    ...
    @Override
    public void dependencies(Closure configureClosure) {
        ConfigureUtil.configure(configureClosure, getDependencies());
    }
    ...
 }
 

看到這里捅暴,是不是會比較清晰一些恬砂,這里調(diào)用的 configure 函數(shù),就是去配置 dependency 的入口蓬痒,我會再后面的章節(jié)繼續(xù)帶著大家深入閱讀的泻骤,現(xiàn)在就先止步于這個(gè)地方。
下一章節(jié)會繼續(xù)上面的話題梧奢,以及如何使用 Plugin狱掂、Convention 等來擴(kuò)展函數(shù)的調(diào)用映射。有篇幅的話還會介紹 NamedDomainObject 相關(guān)一些知識亲轨。敬請期待趋惨。。惦蚊。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末器虾,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蹦锋,更是在濱河造成了極大的恐慌兆沙,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晕粪,死亡現(xiàn)場離奇詭異挤悉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進(jìn)店門装悲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來昏鹃,“玉大人,你說我怎么就攤上這事诀诊《床常” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵属瓣,是天一觀的道長载迄。 經(jīng)常有香客問我,道長抡蛙,這世上最難降的妖魔是什么护昧? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮粗截,結(jié)果婚禮上惋耙,老公的妹妹穿的比我還像新娘。我一直安慰自己熊昌,他們只是感情好绽榛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著婿屹,像睡著了一般灭美。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上昂利,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天届腐,我揣著相機(jī)與錄音,去河邊找鬼页眯。 笑死梯捕,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的窝撵。 我是一名探鬼主播傀顾,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼碌奉!你這毒婦竟也來了短曾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤赐劣,失蹤者是張志新(化名)和其女友劉穎嫉拐,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體魁兼,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡婉徘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盖呼。...
    茶點(diǎn)故事閱讀 39,703評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡儒鹿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出几晤,到底是詐尸還是另有隱情约炎,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布蟹瘾,位于F島的核電站圾浅,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏憾朴。R本人自食惡果不足惜狸捕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望伊脓。 院中可真熱鬧府寒,春花似錦、人聲如沸报腔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽纯蛾。三九已至,卻和暖如春纵隔,著一層夾襖步出監(jiān)牢的瞬間翻诉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工捌刮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留碰煌,地道東北人蚀苛。 一個(gè)月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓怨规,卻偏偏與公主長得像弯淘,于是被迫代替她去往敵國和親饺蔑。 傳聞我的和親對象是個(gè)殘疾皇子授艰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容