用過(guò)android studio的對(duì)gradle應(yīng)該都不陌生了,gradle文件的基本配置大同小異,略做了解使用應(yīng)該是沒(méi)什么問(wèn)題了。但是深入細(xì)致的了解一下對(duì)于理解項(xiàng)目還是很有幫助的,尤其是遇到一些配置復(fù)雜的github項(xiàng)目,不了解gradle可能會(huì)遇到跑不起來(lái)又束手無(wú)策的情形。下面對(duì)gradle相關(guān)知識(shí)、用法做一下總結(jié)。
DSL (domain specific language)
即所謂領(lǐng)域?qū)S谜Z(yǔ)言,其基本思想是“求專不求全”,不像通用目的語(yǔ)言那樣目標(biāo)范圍涵蓋一切軟件問(wèn)題,而是專門針對(duì)某一特定問(wèn)題的計(jì)算機(jī)語(yǔ)言。
-DSL之于程序員正如伽南地之于以色列人,是最初也是最終的夢(mèng)想。幾乎自計(jì)算機(jī)發(fā)明伊始,人們就開始談?wù)揇SL使用DSL了。
-前幾年迅速走紅的Ruby on Rails就被譽(yù)為“Web開發(fā)領(lǐng)域?qū)S谜Z(yǔ)言”
DSL 約等于 整潔的代碼
從概念上說(shuō),程序的編寫過(guò)程就是把業(yè)務(wù)領(lǐng)域中的問(wèn)題通過(guò)代碼或者程序模型表達(dá)出來(lái):
計(jì)算機(jī)的程序模型較為單一(歸根結(jié)底都是運(yùn)算和存儲(chǔ))
在面向?qū)ο蠹夹g(shù)成為主流的今天,通常情況下,計(jì)算機(jī)程序不太可能做到與業(yè)務(wù)領(lǐng)域中的概念一致,或者具有某些直覺(jué)的對(duì)應(yīng)。因此,軟件的修改和可維護(hù)性并沒(méi)有想象中的容易。我們必須不斷地將業(yè)務(wù)領(lǐng)域中的概念轉(zhuǎn)換成相應(yīng)的代碼模型,然后再進(jìn)行修改。這種間接性直接造成了軟件的復(fù)雜度。
而DSL的主要目的就是要消除這樣的復(fù)雜度(或者說(shuō),以構(gòu)造DSL的復(fù)雜度代替這種復(fù)雜度),DSL就要是要以貼近業(yè)務(wù)領(lǐng)域的方式來(lái)構(gòu)造軟件。因此,DSL的簡(jiǎn)潔性往往是一種思維上的簡(jiǎn)潔性,使我們不用費(fèi)太多的氣力就能看懂代碼所對(duì)應(yīng)的業(yè)務(wù)含義。
DSL多以文本代碼的形式出現(xiàn)
多年來(lái)軟件工程實(shí)踐表明文本代碼是最有效率的編輯形式。但是一些特殊領(lǐng)域,文本代碼并不是最佳的表現(xiàn)形式,為了更好的貼近業(yè)務(wù)領(lǐng)域中的概念,我們可能會(huì)選擇使用一些圖形化的DSL。如:[DSM(Domain Specific Modeling)工具GEMS(Generic Eclipse Modeling System)中就大量地使用了不同的圖形化的DSL來(lái)表述系統(tǒng)的各個(gè)不同側(cè)面。
Gradle向我們提供了一整套DSL,所以在很多時(shí)候我們寫的代碼似乎已經(jīng)脫離了groovy,但是在底層依然是執(zhí)行的groovy
為了從命令行運(yùn)行g(shù)radle測(cè)試樣例,首先
配置環(huán)境變量
- 創(chuàng)建變量名:GRADLE_HOME ,變量值:
 C:\Users\jjx.gradle\wrapper\dists\gradle-2.5-all\d3xh0kipe7wr2bvnx5sk0hao8\gradle-2.5
- 加入path
 ;%GRADLE_HOME%\bin;
- 檢查,如下就ok。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 C:\Users\jjx>gradle -v ------------------------------------------------------------ Gradle 2.5 ------------------------------------------------------------ Build time: 2015-07-08 07:38:37 UTC Build number: none Revision: 093765bccd3ee722ed5310583e5ed140688a8c2b Groovy: 2.3.10 Ant: Apache Ant(TM) version 1.9.3 compiled on December 23 2013 JVM: 1.7.0_75 (Oracle Corporation 24.75-b04) OS: Windows 7 6.1 amd64 
初嘗禁果
在d:盤建個(gè)文件夾Test,文件夾下建個(gè)文件build.gradle
打開文件,寫個(gè)簡(jiǎn)單的代碼
| 1 2 3 | task helloWorld << { println "Hello World" } | 
打開cmd, d: 執(zhí)行g(shù)radle helloWorld
| 1 2 3 4 5 6 7 8 9 10 11 12 | C:\Users\jjx>d: D:\>cd Test D:\Test>gradle helloWorld :helloWorld Hello World BUILD SUCCESSFUL Total time: 3.714 secs D:\Test> | 
同時(shí),這時(shí)候發(fā)現(xiàn)已經(jīng)自動(dòng)在Test目錄下創(chuàng)建了.gradle文件。
上面helloWorld后的“<<”表示追加的意思,即向helloWorld中加入執(zhí)行過(guò)程
使用doLast可以達(dá)到同樣效果
| 1 2 3 4 | task helloWorldTwo { doLast { println 'helloWorldTwo'} } | 
如果需要向Task的最前面加入執(zhí)行過(guò)程,我們可以使用doFirst:
| 1 2 3 4 | task helloWorldThree { doFirst { println 'helloWorldThree'} } | 
耶,懂了!
關(guān)于 task
Gradle將當(dāng)前目錄下的build.gradle文件作為項(xiàng)目的構(gòu)建文件。在上面的例子中,我們創(chuàng)建了一個(gè)名為helloWorld的Task,在執(zhí)行g(shù)radle命令時(shí),我們指定執(zhí)行這個(gè)helloWorld Task。這里的helloWorld是一個(gè)DefaultTask類型的對(duì)象,這也是定義一個(gè)Task時(shí)的默認(rèn)類型,當(dāng)然我們也可以顯式地聲明Task的類型,甚至可以自定義一個(gè)Task類型
下面再看一個(gè)小case:
我們?cè)赥est文件夾下建一個(gè)src目錄,建一個(gè)dst目錄,src目錄下建立一個(gè)文件,命名為here.txt
然后在build.gradle中append一個(gè)task:
| 1 2 3 4 5 6 7 | task helloWorld << { println "Hello World" } task copyFile(type: Copy){ from "src" into "dst" } | 
代碼中(type:Copy)就是“顯式地聲明Task的類型”,helloworld沒(méi)有就是默認(rèn)得DefaultTask類型咯。
然后cmd中執(zhí)行命令
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | D:\Test>gradle copyFile :copyFile BUILD SUCCESSFUL Total time: 2.645 secs D:\Test>gradle copyFile :copyFile BUILD SUCCESSFUL Total time: 3.361 secs D:\Test> | 
好了! here.txt也跑到dst中去啦!簡(jiǎn)單吧!
加一把火,我們來(lái)看一下當(dāng)前目錄下(即Test目錄文件,這里也可以將這個(gè)目錄理解為一個(gè)project,不過(guò)還沒(méi)寫有生產(chǎn)力的代碼,哈哈哈)定義的task
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | D:\Test>gradle tasks :tasks ------------------------------------------------------------ All tasks runnable from root project ------------------------------------------------------------ Build Setup tasks ----------------- init - Initializes a new Gradle build. [incubating] wrapper - Generates Gradle wrapper files. [incubating] Help tasks ---------- components - Displays the components produced by root project 'Test'. [incubating] dependencies - Displays all dependencies declared in root project 'Test'. dependencyInsight - Displays the insight into a specific dependency in root project 'Test'. help - Displays a help message. model - Displays the configuration model of root project 'Test'. [incubating] projects - Displays the sub-projects of root project 'Test'. properties - Displays the properties of root project 'Test'. tasks - Displays the tasks runnable from root project 'Test'. Other tasks ----------- copyFile helloWorld To see all tasks and more detail, run gradle tasks --all To see more detail about a task, run gradle help --task <task> BUILD SUCCESSFUL Total time: 2.895 secs D:\Test> | 
是不是很清晰呢,展示了各種task類型,task的作用,以及另外兩個(gè)task相關(guān)的命令,相信聰明的你也一看就懂。
Gradle本身的領(lǐng)域?qū)ο笾饕蠵roject和Task。
Project為Task提供了執(zhí)行上下文,所有的Plugin要么向Project中添加用于配置的Property,要么向Project中添加不同的Task。
一個(gè)Task表示一個(gè)邏輯上較為獨(dú)立的執(zhí)行過(guò)程,比如編譯Java源代碼,拷貝文件,打包Jar文件,甚至可以是執(zhí)行一個(gè)系統(tǒng)命令或者調(diào)用Ant。另外,一個(gè)Task可以讀取和設(shè)置Project的Property以完成特定的操作。是不是很屌的樣子。
task關(guān)鍵字其實(shí)是一個(gè)groovy中方法的調(diào)用,該方法屬于Project,而大括號(hào)之間的內(nèi)容則表示傳遞給task()方法的一個(gè)閉包。
task間的依賴
task之間是可以存在依賴關(guān)系,比如TaskA依賴TaskB,那么在執(zhí)行TaskA時(shí),Gradle會(huì)先執(zhí)行TaskB,再執(zhí)行TaskA。我們可以在定義一個(gè)Task的同時(shí)聲明它的依賴關(guān)系:
| 1 2 3 | task helloWorldFour(dependsOn:helloWorldThree) << { println 'hellohelloWorldFour' } | 
或者
| 1 2 3 4 | task helloWorldFour << { println 'hellohelloWorldFour' } helloWorldFour.dependsOn helloWorldThree | 
可配置的task
一個(gè)Task除了執(zhí)行操作之外,還可以包含多個(gè)Property,其中有Gradle為每個(gè)Task默認(rèn)定義了一些Property,比如description,logger等。
另外,每一個(gè)特定的Task類型還可以含有特定的Property,比如Copy的from和to等。
當(dāng)然,我們還可以動(dòng)態(tài)地向Task中加入額外的Property。在執(zhí)行一個(gè)Task之前,我們通常都需要先設(shè)定Property的值。
| 1 2 3 4 | task helloWorld << { description = "this is helloWorld" println description } | 
或者通過(guò)調(diào)用Task的configure()方法完成Property的設(shè)置:
| 1 2 3 4 5 6 | task helloWorld << { println description } helloWorld.configure { description = "this is helloWorld" } | 
花式task
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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' } | 
對(duì)于每一個(gè)Task,Gradle都會(huì)在Project中創(chuàng)建一個(gè)同名的Property,所以我們可以將該Task當(dāng)作Property來(lái)訪問(wèn),showDescription2便是這種情況。另外,Gradle還會(huì)創(chuàng)建一個(gè)同名的方法,該方法接受一個(gè)閉包,我們可以使用該方法來(lái)配置Task,showDescription3便是這種情況。
耶!簡(jiǎn)單吧
關(guān)于 Groovy
Gradle是一種聲明式的構(gòu)建工具。
在執(zhí)行時(shí),Gradle并不會(huì)一開始便順序執(zhí)行build.gradle文件中的內(nèi)容,而是分為兩個(gè)階段,第一個(gè)階段是配置階段,然后才是實(shí)際的執(zhí)行階段。
配置階段,Gradle將讀取所有build.gradle文件的所有內(nèi)容來(lái)配置Project和Task等,比如設(shè)置Project和Task的Property,處理Task之間的依賴關(guān)系等。
Gradle的DSL只是Groovy語(yǔ)言的內(nèi)部DSL,也必須遵循Groovy的語(yǔ)法規(guī)則。
Groovy語(yǔ)言中的兩個(gè)概念,一個(gè)是Groovy中的Bean概念,一個(gè)是Groovy閉包的delegate機(jī)制。
bean
Groovy中的Bean和Java中的Bean有一個(gè)很大的不同,即Groovy動(dòng)態(tài)的為每一個(gè)字段都會(huì)自動(dòng)生成getter和setter,并且我們可以通過(guò)像訪問(wèn)字段本身一樣調(diào)用getter和setter
| 1 2 3 4 5 6 7 8 | class GroovyBeanExample { private String name } def bean = new GroovyBeanExample() bean.name = 'this is name' //實(shí)際調(diào)用的是"bean.setName('this is name')" println bean.name //實(shí)際調(diào)用的是 "println bean.getName()" | 
采用像直接訪問(wèn)的方式的目的是為了增加代碼的可讀性,使它更加自然,而在內(nèi)部,Groovy依然
是在調(diào)用setter和getter方法。
閉包的delegate機(jī)制
簡(jiǎn)單來(lái)說(shuō),delegate機(jī)制可以使我們將一個(gè)閉包中的執(zhí)行代碼的作用對(duì)象設(shè)置成任意其他對(duì)象。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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 | 
在上面的例子中,當(dāng)調(diào)用configChild()方法時(shí),并沒(méi)有指出name屬性是屬于Child的,但是它的確是在設(shè)置Child的name屬性。
事實(shí)上光從該方法的調(diào)用中,我們根本不知道name是屬于哪個(gè)對(duì)象的,你可能會(huì)認(rèn)為它是屬于Parent的。
真實(shí)情況是,在默認(rèn)情況下,name的確被認(rèn)為是屬于Parent的,但是我們?cè)赾onfigChild()方法的定義中做了手腳,使其不再訪問(wèn)Parent中的name(Parent也沒(méi)有name屬性),而是Child的name。
在configChild()方法中,我們將該方法接受的閉包的delegate設(shè)置成了child,然后將該閉包的ResolveStrategy設(shè)置成了DELEGATE_FIRST。這樣,在調(diào)用configChild()時(shí),所跟閉包中代碼被代理到了child上,即這些代碼實(shí)際上是在child上執(zhí)行的。
此外,閉包的ResolveStrategy在默認(rèn)情況下是OWNER_FIRST,即它會(huì)先查找閉包的owner(這里即parent),如果owner存在,則在owner上執(zhí)行閉包中的代碼。這里我們將其設(shè)置成了DELEGATE_FIRST,即該閉包會(huì)首先查找delegate(本例中即child),如果找到,該閉包便會(huì)在delegate上執(zhí)行。
聯(lián)想gradle中聲明的方法
在使用Gradle時(shí),我們并沒(méi)有像上面的parent.configChild()一樣指明方法調(diào)用的對(duì)象,而是在build.gradle文件中直接調(diào)用task(),apply()和configuration()等方法。這是
因?yàn)樵跊](méi)有說(shuō)明調(diào)用對(duì)象的情況下,Gradle會(huì)自動(dòng)將調(diào)用對(duì)象設(shè)置成當(dāng)前Project。
比如調(diào)用apply()方法和調(diào)用project.apply()方法的效果是一樣的。查查Gradle的Project文檔,你會(huì)發(fā)現(xiàn)這些方法都是Project類的方法。
對(duì)于configurations()方法,該方法實(shí)際上會(huì)將所跟閉包的delegate設(shè)置成ConfigurationContainer,然后在該ConfigurationContainer上執(zhí)行閉包中的代碼。再比如,dependencies()方法,該方法會(huì)將所跟閉包的delegate設(shè)置成DependencyHandler。
終于到了gradle
自定義Property
Gradle還為我們提供了多種方法來(lái)自定義Project的Property。
在build.gradle文件中定義Property
添加一個(gè)名為property1的Property:
| 1
 | ext.property1 = "this is property1"
 | 
或者采用閉包的形式
| 1 2 3 | ext { property2 = "this is property2" } | 
定義了Property后,使用這些Property時(shí)我們則不需要ext,而是可以直接訪問(wèn):
| 1 2 3 4 | task showProperties << { println property1 println property2 } | 
還可以在執(zhí)行命令行的時(shí)候加屬性
| 1 2 3 4 5 6 7 8 | task showCommandLieProperties << { println property3 } //以下是cmd中執(zhí)行命令 gradle -Property3="this is property3" showCommandLieProperties //通過(guò)JVM系統(tǒng)參數(shù)定義Property,與java類似,但是前面要約定以“org.gradle.project”為前綴 gradle -D org.gradle.project.property3="this is another property3" showCommandLieProperties | 
此外還可以通過(guò)環(huán)境變量來(lái)為Gradle設(shè)置Property,但是每一個(gè)Property都需要以“ORG_GRADLEPROJECT”為前綴:
| 1
 | ORG_GRADLE_PROJECT_property3="this is yet another property3"
 | 
Gradle 的 Plugin
Gradle最常用的Plugin便是java Plugin了。和其他Plugin一樣,java Plugin并沒(méi)有什么特別的地方,只是向Project中引入了多個(gè)Task和Property。當(dāng)然,java Plugin也有比較與眾不同的地方,其中之一便是它在項(xiàng)目中引入了構(gòu)建生命周期的概念,就像Maven一樣。但是,和Maven不同的是,Gradle的項(xiàng)目構(gòu)建生命周期并不是Gradle的內(nèi)建機(jī)制,而是由Plugin自己引入的。
依賴管理
一個(gè)項(xiàng)目總會(huì)依賴于第三方,要么是一個(gè)第三方類庫(kù),要么是自己開發(fā)的另一個(gè)module
配置Gradle的Repository,就是告訴Gradle在什么地方去獲取這些依賴
| 1 2 3 4 | repositories { mavenCentral() jCentral() } | 
jCentral()是大于mavenCentral()的一個(gè)倉(cāng)庫(kù),現(xiàn)在是studio默認(rèn)的倉(cāng)庫(kù)
Gradle對(duì)依賴進(jìn)行分組,允許編譯時(shí)使用一組依賴,運(yùn)行時(shí)使用另一組依賴。每一組依賴稱為一個(gè)Configuration,在聲明依賴時(shí),我們實(shí)際上是在設(shè)置不同的Configuration。
要定義一個(gè)Configuration,我們可以通過(guò)以下方式完成:studio一般不需要設(shè)置,應(yīng)該是有默認(rèn)的,即為classpath
| 1 2 3 | configurations { myDependency } | 
通過(guò)dependencies()方法向myDependency中加入實(shí)際的依賴項(xiàng):
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | dependencies { //下面的myDependency是關(guān)鍵 myDependency 'org.apache.commons:commons-lang3:3.0' } //類似studio中的classpath dependencies { classpath 'com.android.tools.build:gradle:1.3.0' } //還有 這里的compile,testCompile dependencies { compile project(':library') compile 'com.android.support:recyclerview-v7:22.2.1' compile 'com.android.support:design:22.2.1' compile 'com.evernote:android-intent:1.0.1' testCompile 'junit:junit:4.8.2' } | 
myDependency,classpath,compile,testCompile都是Configuration(一組依賴)。
除了myDependency都不使我們定義的,為啥呢,android Plugin會(huì)自動(dòng)定義compile和testCompile分別用于編譯Java源文件和編譯Java測(cè)試源文件。classpath應(yīng)該是用于所有,我類推的。
Gradle還允許我們聲明對(duì)其他Project或者文件系統(tǒng)的依賴。
| 1 2 3 4 | dependencies { //library是另一個(gè)module的名字 compile project(':library') } | 
對(duì)于本地文件系統(tǒng)中的Jar文件,我們可以通過(guò)以下方式聲明對(duì)其的依賴:
| 1 2 3 4 5 6 7 | dependencies { //java compile files('spring-core.jar', 'spring-aap.jar') compile fileTree(dir: 'deps', include: '*.jar') //studio中一般這么寫 compile fileTree(dir: 'libs', include: ['*.jar']) } | 
構(gòu)建多module的project
Gradle為每個(gè)build.gradle都會(huì)創(chuàng)建一個(gè)相應(yīng)的module領(lǐng)域?qū)ο?,在編寫Gradle腳本時(shí),我們實(shí)際上是在操作諸如module這樣的Gradle領(lǐng)域?qū)ο蟆T诙鄊odule的項(xiàng)目中,我們會(huì)操作多個(gè)module領(lǐng)域?qū)ο蟆radle提供了強(qiáng)大的多module構(gòu)建支持
要?jiǎng)?chuàng)建多module的Gradle項(xiàng)目,我們首先需要在根(Root)Project中加入名為settings.gradle的配置文件,該文件應(yīng)該包含各個(gè)子module(其實(shí)就是一個(gè)子project)的名稱。如setting.gradle中:
| 1
 | include 'library', 'demo'
 | 
類似module(子project)的build.gradle,(Root)Project也有自己的build.gradle,在里面通常設(shè)置:
| 1 2 3 4 5 6 7 8 9 10 | allprojects { repositories { jcenter() } //通常studio項(xiàng)目沒(méi)有,咱自己加的 apply plugin: 'idea' task allTask << { println project.name } } | 
allprojects()方法將repositories配置一次性地應(yīng)用于所有的module(子Project)和root-project本身,當(dāng)然也包括定義的Task,這個(gè)task配置到所有module里面了和root-project。
subprojects()方法用于配置所有的子Project(不包含根Project)
步入巔峰
Gradle本身只是一個(gè)架子,真正起作用的是Task和Plugin。
自定義Task
Gradle中的Task要么是由不同的Plugin引入的,要么是我們自己在build.gradle文件中直接創(chuàng)建的。
- 
在build.gradle文件中直接定義 
 需要定義的Task類型不多時(shí)
 Gradle其實(shí)就是groovy代碼,所以在build.gradle文件中,我們便可以定義Task類。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class HelloWorldTask extends DefaultTask { //@Optional,表示在配置該Task時(shí),message是可選的。 @Optional String message = 'I am jjx' //@TaskAction表示該Task要執(zhí)行的動(dòng)作,即在調(diào)用該Task時(shí),hello()方法將被執(zhí)行 @TaskAction def hello(){ println "hello world $message" } } //hello使用了默認(rèn)的message值 task hello(type:HelloWorldTask) //重新設(shè)置了message的值 task helloOne(type:HelloWorldTask){ message ="I am a android developer" } 
- 
在當(dāng)前工程中定義Task類型 
 只能應(yīng)用在當(dāng)前module中,沒(méi)什么卵用,下面是全局可用的
- 在單獨(dú)的項(xiàng)目中定義Task類型
 項(xiàng)目中存在大量的自定義Task類型時(shí),在另外的一個(gè)gradle文件中定義這些Task,然后再apply到build.gradle文件中。
 可以參考印象筆記的demo:https://github.com/evernote/evernote-sdk-android
 中的:1 2 3 4 //這是插件 apply plugin: 'com.android.application' //這里gradle-quality.gradle就是另外單獨(dú)定義了task的gradle apply from: '../build-config/gradle-quality.gradle' 
自定義Plugin
與自定義task極其類似,可以類推理解,也是有3中方式定義,只是代碼不一樣:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | apply plugin: DateAndTimePlugin dateAndTime { timeFormat = 'HH:mm:ss.SSS' dateFormat = 'MM/dd/yyyy' } class DateAndTimePlugin implements Plugin<Project> { //該接口定義了一個(gè)apply()方法,在該方法中,我們可以操作Project, //比如向其中加入Task,定義額外的Property等。 void apply(Project project) { project.extensions.create("dateAndTime", DateAndTimePluginExtension) project.task('showTime') << { println "Current time is " + new Date().format(project.dateAndTime.timeFormat) } project.tasks.create('showDate') << { println "Current date is " + new Date().format(project.dateAndTime.dateFormat) } } } //每個(gè)Gradle的Project都維護(hù)了一個(gè)ExtenionContainer, //我們可以通過(guò)project.extentions進(jìn)行訪問(wèn) //比如讀取額外的Property和定義額外的Property等。 //向Project中定義了一個(gè)名為dateAndTime的extension //并向其中加入了2個(gè)Property,分別為timeFormat和dateFormat class DateAndTimePluginExtension { String timeFormat = "MM/dd/yyyyHH:mm:ss.SSS" String dateFormat = "yyyy-MM-dd" } | 

 
                         
                                
 
                                







 
                        