Gradle學習系列之三——讀懂Gradle語法


  在本系列上篇文章中,我們講到了創建Task的多種方法,在本篇文章中,我們將學習如何讀懂Gradle。

 

  請通過以下方式下載本系列文章的Github示例代碼:

git clone https://github.com/davenkin/gradle-learning.git

 


   Gradle是一種聲明式的構建工具。在執行時,Gradle並不會一開始便順序執行build.gradle文件中的內容,而是分為兩個階段,第一個階段是配置階段,然后才是實際的執行階段。在配置階段,Gradle將讀取所有build.gradle文件的所有內容來配置Project和Task等,比如設置Project和Task的Property,處理Task之間的依賴關系等。

  雖然很多時候我們只需要照着網上的例子寫自己的DSL語句就行了,但是此時我們所知道的也就只有這么多了。如果我們能夠了解Gradle DSL的內部工作機制,那么我們便可以達到舉一反三的效果。在前面的文章中我們講到,Gradle的DSL只是Groovy語言的內部DSL,也即必須遵循Groovy的語法規則。現在,讓我們先來看看以下非常簡單的Task:

task showDescription1 << { description = 'this is task showDescription' println description } task showDescription2 << { println description } showDescription2.description = 'this is task showDescription' task showDescription3 << { println description } showDescription3 { description = 'this is task showDescription' }

 

   以上3個Task完成的功能均相同,即先設置Task的description屬性,在將其輸出到命令行。但是,他們對description的設置方式是不同的。對於showDescription1,我們在定義一個Task的同時便設置description;對於showDescription2,其本身便是Project的一個Property;而對於showDescription3,我們是在一個和它同名的方法中設置description。

  事實上,對於每一個Task,Gradle都會在Project中創建一個同名的Property,所以我們可以將該Task當作Property來訪問,showDescription2便是這種情況。另外,Gradle還會創建一個同名的方法,該方法接受一個閉包,我們可以使用該方法來配置Task,showDescription3便是這種情況。

  要讀懂Gradle,我們首先需要了解Groovy語言中的兩個概念,一個Groovy中的Bean概念,一個是Groovy閉包的delegate機制。

  Groovy中的Bean和Java中的Bean有一個很大的不同,即Groovy為每一個字段都會自動生成getter和setter,並且我們可以通過像訪問字段本身一樣調用getter和setter,比如:

class GroovyBeanExample { private String name } def bean = new GroovyBeanExample() bean.name = 'this is name' println bean.name

 

   我們看到,GroovyBeanExample只定義了一個私有的name屬性,並沒有getter和setter。但是在使用時,我們可以直接對name進行訪問,無論時讀還是寫。事實上,我們並不是在直接訪問name屬性,當我們執行"bean.name = 'this is name'"時,我們實際調用的是"bean.setName('this is name')",而在調用"println bean.name"時,我們實際調用的是"println bean.getName()"。這里的原因在於,Groovy動態地為name創建了getter和setter,采用像直接訪問的方式的目的是為了增加代碼的可讀性,使它更加自然,而在內部,Groovy依然是在調用setter和getter方法。這樣,我們便可以理解上面對showDescription2的description設置原理。

  另外,Gradle大量地使用了Groovy閉包的delegate機制。簡單來說,delegate機制可以使我們將一個閉包中的執行代碼的作用對象設置成任意其他對象。比如:

class Child { private String name } class Parent { Child child = new Child(); void configChild(Closure c) { c.delegate = child c.setResolveStrategy Closure.DELEGATE_FIRST c() } } def parent = new Parent() parent.configChild { name = "child name" } println parent.child.name

 

  在上面的例子中,當我們調用configChild()方法時,我們並沒有指出name屬性是屬於Child的,但是它的確是在設置Child的name屬性。事實上光從該方法的調用中,我們根本不知道name是屬於哪個對象的,你可能會認為它是屬於Parent的。真實情況是,在默認情況下,name的確被認為是屬於Parent的,但是我們在configChild()方法的定義中做了手腳,使其不再訪問Parent中的name(Parent也沒有name屬性),而是Child的name。在configChild()方法中,我們將該方法接受的閉包的delegate設置成了child,然后將該閉包的ResolveStrategy設置成了DELEGATE_FIRST。這樣,在調用configChild()時,所跟閉包中代碼被代理到了child上,即這些代碼實際上是在child上執行的。此外,閉包的ResolveStrategy在默認情況下是OWNER_FIRST,即它會先查找閉包的owner(這里即parent),如果owner存在,則在owner上執行閉包中的代碼。這里我們將其設置成了DELEGATE_FIRST,即該閉包會首先查找delegate(本例中即child),如果找到,該閉包便會在delegate上執行。對於上面的showDescription3,便是這種情況。當然,實際情況會稍微復雜一點,比如showDescription3()方法會在內部調用showDescription3的configure()方法,再在configure()方法中執行閉包中的代碼。

  你可能會發現,在使用Gradle時,我們並沒有像上面的parent.configChild()一樣指明方法調用的對象,而是在build.gradle文件中直接調用task(),apply()和configuration()方法等,這是因為在沒有說明調用對象的情況下,Gradle會自動將調用對象設置成當前Project。比如調用apply()方法和調用project.apply()方法的效果是一樣的。查查Gradle的Project文檔,你會發現這些方法都是Project類的方法。

  另外舉個例子,對於configurations()方法(它的作用我們將在后面的文章中講到),該方法實際上會將所跟閉包的delegate設置成ConfigurationContainer,然后在該ConfigurationContainer上執行閉包中的代碼。再比如,dependencies()方法,該方法會將所跟閉包的delegate設置成DependencyHandler。

  還有,Project還定義了configure(Object object,Closure configureClosure)方法,該方法是專門用來配置對象的(比如Task),它會將configureClosure的delegate設置成object,之后configureClosure中的執行代碼其實是在object上執行的。和Groovy Bean一樣,delegate機制的一個好處是可以增加所創建DSL的可讀性。

  在下一篇文章中,我們將講到如何進行增量式構建。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM