Groovy Closure & Action

Android如今的構建工具用的是gradle,很早之前就有過接觸,只不過歷來沒有用到實際的項目中。java

這兩天在看gradle的一些官方文檔和官方推薦的書,並結合着項目的build腳本。對gradle及其所用的groovy語言大體有了個認識。不過時間一直有一個問題在困擾我:android

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

如上述代碼所示 buildTypes 的是一個method,那麼其後的block應該是一個closure,可是buildTypes的方法聲明倒是這樣的api

    /**
     * Configures the build types.
     */
    void buildTypes(Action> action) {
        checkWritability()
        action.execute(buildTypes)
    }

這裏,我就納悶了,明明接受的是一個Action 類型的參數啊,爲啥build腳本里面給的是一個Closure呢?我第一反應,確定是哪裏把Closure強轉成了Action(事實證實,第一反應得方向是對的,哈哈)。函數

不過我找了N久,就是找不到哪裏調用了buildTypes,並在這以前把參數類型作了強轉。尼瑪,糾結了很久。後來我就想,既然在代碼裏面找不到直接的轉換的代碼,是否是多是在gradle內部作了呢?工具

順着這個思路,我本身寫了個gradle腳本,很簡單gradle

    def buildTypes(Action action) {
        println ''
        def text = "action is called here"
        action.execute(text)
    }

    interface Command {
        void balabala(String test);
    }

    def testCommand(Command test) {
        println ''
        test.balabala("balabala......")
    }

    task hello << {
        println 'hello task is called'

        buildTypes {
            println 'Action for buildTypes'
        }

        testCommand { String para ->
            println 'Command->' + para
        }
    }

       上面這段代碼的運行結果以下:ui

       

       能夠看到task hello中buildTypes和testCommand方法後的closure都已經被調用了,並且經過Command接口中的balabala方法傳遞的參數,testCommand後的closure也能收到。this

那麼能夠肯定的是,Closure必定在某個時候被強轉成了Action(其實對了一半)。另外能夠看到不單單是gradle本身的Action接口能夠這樣,本身寫得Command接口也是能夠的,因此url

幹這個事情的很大程度上應該不是gradle,那麼剩下的應該就是groovy啦。code

 

     因而我又作了一個實驗,直接寫一個groovy的腳原本驗證一下

    interface Command {
        void excute(String a, Integer b)
    }

    def buildTypes(Command cmd) {
        println '++++++++buildTypes was called'
        cmd.excute('meituan', 2015)
    }

    buildTypes { String a, Integer b ->
        println 'fairysword ' + a + '###' + b
    }

    buildTypes { String a, Integer b ->
        println 'uabearbest ' + a + '@@@' + b
    }

        上面這段腳本運行結果以下

        

        嗯,到這裏,應該能夠看出來,的確是groovy乾的,不過怎麼幹的,咱仍是不知道。不過咱們知道groovy和java都是運行在JVM上,groovy也是編譯成java字節碼的,

因此,我試着把上述腳本直接編譯後,研究一下。先把腳本編譯成class文件,在反編譯成java文件。結果以下

/*
 * Decompiled with CFR 0_102.
 * 
 * Could not load the following classes:
 *  Command
 *  groovy.lang.Binding
 *  groovy.lang.GroovyObject
 *  groovy.lang.MetaClass
 *  groovy.lang.Script
 *  org.codehaus.groovy.reflection.ClassInfo
 *  org.codehaus.groovy.runtime.InvokerHelper
 *  org.codehaus.groovy.runtime.ScriptBytecodeAdapter
 *  org.codehaus.groovy.runtime.callsite.CallSite
 *  org.codehaus.groovy.runtime.callsite.CallSiteArray
 *  test$_run_closure1
 *  test$_run_closure2
 */

import Command;
import groovy.lang.Binding;
import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import groovy.lang.Script;

import org.codehaus.groovy.reflection.ClassInfo;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;
import org.codehaus.groovy.runtime.callsite.CallSiteArray;

import test;

public class test
        extends Script {
    private static /* synthetic */ ClassInfo $staticClassInfo;
    public static transient /* synthetic */ boolean __$stMC;
    private static /* synthetic */ SoftReference $callSiteArray;

    public test() {
        test test;
        CallSite[] arrcallSite = test.$getCallSiteArray();
    }

    public test(Binding context) {
        CallSite[] arrcallSite = test.$getCallSiteArray();
        super(context);
    }

    public static /* varargs */ void main(String... args) {
        CallSite[] arrcallSite = test.$getCallSiteArray();
        arrcallSite[0].call((Object) InvokerHelper.class, (Object) test.class, (Object) args);
    }

    public Object run() {
        CallSite[] arrcallSite = test.$getCallSiteArray();
        arrcallSite[1].callCurrent((GroovyObject) this, (Object) new _run_closure1((Object) this, (Object) this));
        return arrcallSite[2].callCurrent((GroovyObject) this, (Object) new _run_closure2((Object) this, (Object) this));
    }

    public Object buildTypes(Command cmd) {
        CallSite[] arrcallSite = test.$getCallSiteArray();
        arrcallSite[3].callCurrent((GroovyObject) this, (Object) "++++++++buildTypes was called");
        return arrcallSite[4].call((Object) cmd, (Object) "xiong", (Object) 1987);
    }

    protected /* synthetic */ MetaClass $getStaticMetaClass() {
        if (this.getClass() != test.class) {
            return ScriptBytecodeAdapter.initMetaClass((Object) this);
        }
        ClassInfo classInfo = $staticClassInfo;
        if (classInfo == null) {
            $staticClassInfo = classInfo = ClassInfo.getClassInfo(this.getClass());
        }
        return classInfo.getMetaClass();
    }

    private static /* synthetic */ void $createCallSiteArray_1(String[] arrstring) {
        arrstring[0] = "runScript";
        arrstring[1] = "buildTypes";
        arrstring[2] = "buildTypes";
        arrstring[3] = "println";
        arrstring[4] = "excute";
    }

    private static /* synthetic */ CallSiteArray $createCallSiteArray() {
        String[] arrstring = new String[5];
        test.$createCallSiteArray_1(arrstring);
        return new CallSiteArray((Class) test.class, arrstring);
    }

    private static /* synthetic */ CallSite[] $getCallSiteArray() {
        CallSiteArray callSiteArray;
        if ($callSiteArray == null || (callSiteArray = (CallSiteArray) $callSiteArray.get()) == null) {
            callSiteArray = test.$createCallSiteArray();
            $callSiteArray = new SoftReference<CallSiteArray>(callSiteArray);
        }
        return callSiteArray.array;
    }
}

    研究這段代碼,能夠看出所用的調用點(即groovy所謂的CallSite)都被groovy統一作了處理,在這個groovy的腳本中總共存在5處調用

    private static /* synthetic */ void $createCallSiteArray_1(String[] arrstring) {
        arrstring[0] = "runScript";
        arrstring[1] = "buildTypes";
        arrstring[2] = "buildTypes";
        arrstring[3] = "println";
        arrstring[4] = "excute";
    }

    對buildTypes的兩處調用體如今run函數中

public Object run() {
    CallSite[] arrcallSite = test.$getCallSiteArray();
    arrcallSite[1].callCurrent((GroovyObject) this, (Object) new _run_closure1((Object) this, (Object) this));
    return arrcallSite[2].callCurrent((GroovyObject) this, (Object) new _run_closure2((Object) this, (Object) this));
}

    至此,咱們終於能夠看到groovy並無把Closure轉成Action,而是無差異的都轉成了Object,這也解釋了爲啥buildTypes方法接受的是Command類型的參數,可是實際上你傳給他一個Closure參數,也能正常work

相關文章
相關標籤/搜索