Gradle中的閉包
Gradle是基於Groovy的DSL基礎上的構建工具,Gradle中的閉包,其原型上實際上即Groovy中閉包。而在表現形式上,其實,Gradle更多的是以 約定
和基於約定基礎上的 配置
去展現。但本質上,大多數 配置
,實際上都對應著閉包以及閉包的具體使用。
例如,實際Android專案中,我們經常看到類似如下的所謂配置項:
allprojects { repositories { mavenLocal() maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } google() jcenter() } configurations.all { resolutionStrategy.cacheChangingModulesFor 1, 'seconds' } } task clean(type: Delete) { delete rootProject.buildDir delete "${rootProject.rootDir}/jenkinsOut" } 複製程式碼
當我們在 allprojects
上按住command鍵時,發現有如下圖所示的提示。
出現的提示指的是此配置項所對應的Gradle中原型,可以點選直接進入對應的Gradle API。
/** * <p>Configures this project and each of its sub-projects.</p> * * <p>This method executes the given closure against this project and its sub-projects. The target {@link Project} * is passed to the closure as the closure's delegate.</p> * * @param configureClosure The closure to execute. */ void allprojects(Closure configureClosure); 複製程式碼
我們發現,我們常用的 allprojects
配置,實際上真正對應著的,是一個 void allprojects(Closure configureClosure)
Java方法,而其後 {}
中的配置,實際上整體是一個 Closure
型別的引數,在方法說明中,指出 這個方法是為當前專案及其子專案執行給定的閉包,目標@Project作為閉包的委託傳遞給閉包
。
於是,到底什麼是閉包,閉包具體的運作機制是怎麼樣的,有必要實際窺探一番。
點選Gradle API中的Closure,可以進入對應的Closure型別宣告,實際上對應的是Groovy jar包中的class檔案宣告。
package groovy.lang; import ... public abstract class Closure<V> extends GroovyObjectSupport implements Cloneable, Runnable, GroovyCallable<V>, Serializable { 複製程式碼
Closure
,翻譯過來是 閉包
,在JS等語言中也存在閉包的概念,但是,不同語言中,對於閉包的具體描述或實際的應用,不同語言,可能還有所不同。
先了解一下 Groovy
中 閉包
的描述:
閉包,是一個程式碼塊,或可以理解成一個匿名函式,在外部方法呼叫時,可以將其作為方法的實參傳遞給方法的形參,並在方法內部回撥此匿名函式,且回撥此匿名函式時可以傳遞實參給到匿名函式的內部去接收,並執行此匿名函式。 同時,此程式碼塊或匿名函式也可以賦值給一個變數,使其具有自執行的能力,且最後一行的執行語句作為匿名函式的返回。 複製程式碼
看著好像不太容易理解,可以具體看幾個實際例子。
// 1,定義一個閉包,賦值給一個變數,並進行顯示的自我呼叫。 def t = { println "HelloClosure" } // 此處也可以寫成t.call() t() // 執行後,輸出結果為: HelloClosure 複製程式碼
其中,以變數的方式呼叫閉包 t()
與 t.call()
是等價的。
// 2,定義一個閉包,賦值給一個變數,並進行顯示的自我呼叫,並檢測其返回值 def t = { println "HelloClosure" "ttt" } println "closure return: " + t.call() // 執行後,輸出結果為: HelloClosure closure return: ttt 複製程式碼
// 3,定義一個閉包,賦值給一個變數,並進行顯示的自我呼叫,呼叫時向閉包傳遞實參 def t = { println "HelloClosure, the param value is: " + it } t("mm") // 執行後,輸出結果為: HelloClosure, the param value is: mm 複製程式碼
呼叫閉包時,如果向閉包傳遞實參,閉包內部如果沒有宣告形參接收,預設是以it的變數的一個形參去接收實參。
因此,例3實際上是等價於:
def t = { it -> println "HelloClosure, the param value is: " + it } t("mm") 複製程式碼
// 4,如果閉包中顯示的聲明瞭形參,則以顯示的宣告的形參去接收實參 def t = { x, y -> println "HelloClosure, the param value is: " + x + ", " + y } t("mm", "nn") // 執行後,輸出結果為: HelloClosure, the param value is: mm, nn 複製程式碼
以上,都是將閉包賦值給一個變數後,進行的閉包的呼叫行為。
同時,我們也可以將閉包作為一個方法實參,在方法呼叫時傳遞給方法形參,然後方法內部形成對此閉包的回撥。
// 5,將閉包作為一個方法實參,在方法呼叫時傳遞給方法形參,然後方法內部形成對此閉包的回撥 class Person { String getName(Closure closure) { closure("cc", "dd") } } def t = { x, y -> println "HelloClosure, the param value is: " + x + ", " + y } new Person().getName(t) // 執行後,輸出結果為: HelloClosure, the param value is: cc, dd 複製程式碼
例5中的閉包如果沒有事先賦值給變數 t
,而也可以直接使用,效果等價於:
class Person { String getName(Closure closure) { closure("cc", "dd") } } new Person().getName({ x, y -> println "HelloClosure, the param value is: " + x + ", " + y }) 複製程式碼
閉包作為方法中的最後一個引數,可以從 ()
中拿出來,則等價於:
new Person().getName(){ x, y -> println "HelloClosure, the param value is: " + x + ", " + y } 複製程式碼
同時,方法後的 ()
可以去掉,則等價於:
new Person().getName { x, y -> println "HelloClosure, the param value is: " + x + ", " + y } 複製程式碼
如果外部呼叫閉包的方法傳遞實參時,沒有傳遞實參或只傳遞了一個引數(如果沒有傳遞實參,則it為null),則進一步演化成:
class Person { String getName(Closure closure) { closure("cc") } } new Person().getName { println "HelloClosure, the param value is: " + it } 複製程式碼
這也就是我們在Gradle中經常見到的閉包形式,即表面上只有 {}
的配置形式。
將閉包理解成一個特殊的匿名函式,無論是通過變數的自呼叫,還是作為方法實參的傳遞後,在方法內部被回撥,閉包的最後一行執行被當做匿名函式的整體返回,都可以很好的得以理解。同時,也能容易的理解閉包可以巢狀使用等(即當做匿名函式的巢狀)。
閉包在 Gradle
中的配置中,被大量使用。理解 Gradle
中的閉包,對一些特殊的寫法,如Gradle構建生命週期中的閉包回撥中的實參使用等,可以有很好的運用。
如常見的在 Gradle
構建的 配置階段
中的 afterEvaluate
hook
中,可以設定相關 task
的依賴關係等。此時, it
接收的是回傳進來的當前 project
實參。
afterEvaluate { ... Task assembleJenkinsTask = rootProject.tasks.getByName('assembleJenkins') Task unitTestTask = it.tasks.findByName('testDebugUnitTest') if (unitTestTask != null) { assembleJenkinsTask.dependsOn unitTestTask } ... } 複製程式碼
在一定意義上, Groovy
中閉包的概念,以及其實際的用法上,實質上根 Java 8
中的 lambda
表示式具有很相近的含義。