Java使用自動(dòng)化部署工具Gradle中的任務(wù)設(shè)定教程
tasks
下面的代碼展示了三個(gè)Gradle task,稍后會講解這三者的不同。
task myTask { println "Hello, World!" } task myTask { doLast { println "Hello, World!" } } task myTask << { println "Hello, World!" }
我的目的是創(chuàng)建一個(gè)task,當(dāng)它執(zhí)行的時(shí)候會打印出來”Hello, World!”。當(dāng)我第一次創(chuàng)建task的時(shí)候,我猜測應(yīng)該是這樣來寫的:
task myTask { println "Hello, World!" }
現(xiàn)在,試著來執(zhí)行這個(gè)myTask,在命令行輸入gradle myTask,打印如下:
user$ gradle myTask Hello, World! :myTask UP-TO-DATE
這個(gè)task看起來起作用了。它打印了”Hello, World!”。
但是,它其實(shí)并沒有像我們期望的那樣。下面我們來看看為什么。在命令行輸入gradle tasks來查看所有可用的tasks。
user$ gradle tasks Hello, World! :tasks ------------------------------------------------------------ All tasks runnable from root project ------------------------------------------------------------ Build Setup tasks ----------------- init - Initializes a new Gradle build. [incubating] ..........
等等,為什么”Hello, World!”打印出來了?我只是想看看有哪些可用的task,并沒有執(zhí)行任何自定義的task!
原因其實(shí)很簡單,Gradle task在它的生命周期中有兩個(gè)主要的階段:配置階段 和 執(zhí)行階段。
可能我的用詞不是很精確,但這的確能幫助我理解tasks。
Gradle在執(zhí)行task之前都要對task先進(jìn)行配置。那么問題就來了,我怎么知道我的task中,哪些代碼是在配置過程中執(zhí)行的,哪些代碼是在task執(zhí)行的時(shí)候運(yùn)行的?答案就是,在task的最頂層的代碼就是配置代碼,比如:
task myTask { def name = "Pavel" //<-- 這行代碼會在配置階段執(zhí)行 println "Hello, World!"http:////<-- 這行代碼也將在配置階段執(zhí)行 }
這就是為什么我執(zhí)行g(shù)radle tasks的時(shí)候,會打印出來”Hello, World!”-因?yàn)榕渲么a被執(zhí)行了。但這并不是我想要的效果,我想要”Hello, World!”僅僅在我顯式的調(diào)用myTask的時(shí)候才打印出來。為了達(dá)到這個(gè)效果,最簡單的方法就是就是使用Task#doLast()方法。
task myTask { def text = 'Hello, World!' //configure my task doLast { println text //this is executed when my task is called } }
現(xiàn)在,”Hello, World!”僅僅會在我執(zhí)行g(shù)radle myTask的時(shí)候打印出來。Cool,現(xiàn)在我已經(jīng)知道如何配置以及使task做正確的事情。還有一個(gè)問題,最開始的例子中,第三個(gè)task的<<符號是什么意思?
task myTask2 << { println "Hello, World!" }
這其實(shí)只是doLast的一個(gè)語法糖版本。它和下面的寫法效果是一樣的:
task myTask { doLast { println 'Hello, World!' //this is executed when my task is called } }
但是,這種寫法所有的代碼都在執(zhí)行部分,沒有配置部分的代碼,因此比較適合那些簡小不需要配置的task。一旦你的task需要配置,那么還是要使用doLast的版本。
語法
Gradle腳本是使用Groovy語言來寫的。Groovy的語法有點(diǎn)像Java,希望你能接受它。
如果你對Groovy已經(jīng)很熟悉了,可以跳過這部分了。
Groovy中有一個(gè)很重要的概念你必要要弄懂–Closure(閉包)
Closures
Closure是我們弄懂Gradle的關(guān)鍵。Closure是一段單獨(dú)的代碼塊,它可以接收參數(shù),返回值,也可以被賦值給變量。和Java中的Callable接口,F(xiàn)uture類似,也像函數(shù)指針,你自己怎么方便理解都好。。。
關(guān)鍵是這塊代碼會在你調(diào)用的時(shí)候執(zhí)行,而不是在創(chuàng)建的時(shí)候??匆粋€(gè)Closure的例子:
def myClosure = { println 'Hello world!' } //execute our closure myClosure() #output: Hello world!
下面是一個(gè)接收參數(shù)的Closure:
def myClosure = {String str -> println str } //execute our closure myClosure('Hello world!') #output: Hello world!
如果Closure只接收一個(gè)參數(shù),可以使用it來引用這個(gè)參數(shù):
def myClosure = {println it } //execute our closure myClosure('Hello world!') #output: Hello world!
接收多個(gè)參數(shù)的Closure:
def myClosure = {String str, int num -> println "$str : $num" } //execute our closure myClosure('my string', 21) #output: my string : 21
另外,參數(shù)的類型是可選的,上面的例子可以簡寫成這樣:
def myClosure = {str, num -> println "$str : $num" } //execute our closure myClosure('my string', 21) #output: my string : 21
很酷的是Closure中可以使用當(dāng)前上下文中的變量。默認(rèn)情況下,當(dāng)前的上下文就是closure被創(chuàng)建時(shí)所在的類:
def myVar = 'Hello World!' def myClosure = {println myVar} myClosure() #output: Hello world!
另外一個(gè)很酷的點(diǎn)是closure的上下文是可以改變的,通過Closure#setDelegate()。這個(gè)特性非常有用:
def myClosure = {println myVar} //I'm referencing myVar from MyClass class MyClass m = new MyClass() myClosure.setDelegate(m) myClosure() class MyClass { def myVar = 'Hello from MyClass!' } #output: Hello from MyClass!
正如你鎖看見的,在創(chuàng)建closure的時(shí)候,myVar并不存在。這并沒有什么問題,因?yàn)楫?dāng)我們執(zhí)行closure的時(shí)候,在closure的上下文中,myVar是存在的。這個(gè)例子中。因?yàn)槲以趫?zhí)行closure之前改變了它的上下文為m,因此myVar是存在的。
把closure當(dāng)做參數(shù)傳遞
closure的好處就是可以傳遞給不同的方法,這樣可以幫助我們解耦執(zhí)行邏輯。前面的例子中我已經(jīng)展示了如何把closure傳遞給一個(gè)類的實(shí)例。下面我們將看一下各種接收closure作為參數(shù)的方法:
1.只接收一個(gè)參數(shù),且參數(shù)是closure的方法: myMethod(myClosure)
2.如果方法只接收一個(gè)參數(shù),括號可以省略: myMethod myClosure
3.可以使用內(nèi)聯(lián)的closure: myMethod {println ‘Hello World'}
4.接收兩個(gè)參數(shù)的方法: myMethod(arg1, myClosure)
5.和4類似,單數(shù)closure是內(nèi)聯(lián)的: myMethod(arg1, { println ‘Hello World' })
6.如果最后一個(gè)參數(shù)是closure,它可以從小括號從拿出來: myMethod(arg1) { println ‘Hello World' }
這里我只想提醒你一下,3和6的寫法是不是看起來很眼熟?
Gradle例子
現(xiàn)在我們已經(jīng)了解了基本的語法了,那么如何在Gradle腳本中使用呢?先看下面的例子吧:
buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.2.3' } } allprojects { repositories { jcenter() } }
知道了Groovy的語法,是不是上面的例子就很好理解了?
首先就是一個(gè)buildscript方法,它接收一個(gè)closure:
def buildscript(Closure closure)
接著是allprojects方法,它也接收一個(gè)closure參數(shù):
def allprojects(Closure closure)
其他的都類似。。。
現(xiàn)在看起來容易多了,但是還有一點(diǎn)不明白,那就是這些方法是在哪里定義的?答案就是Project
Project
這是理解Gradle腳本的一個(gè)關(guān)鍵。
構(gòu)建腳本頂層的語句塊都會被委托給Project的實(shí)例
這就說明Project正是我要找得地方。
在Project的文檔頁面搜索buildscript方法,會找到buildscript{} script block(腳本塊).等等,script block是什么鬼?根據(jù)文檔:
script block就是只接收closure作為參數(shù)的方法
繼續(xù)閱讀buildscript的文檔,文檔上說Delegates to: ScriptHandler from buildscript。也就是說,我們傳遞給buildscript方法的closure,最終執(zhí)行的上下文是ScriptHandler。在上面的例子中,我們的傳遞給buildscript的closure調(diào)用了repositories(closure)和dependencies(closure)方法。既然closure被委托給了ScriptHandler,那么我們就去ScriptHandler中尋找dependencies方法。
找到了void dependencies(Closure configureClosure),根據(jù)文檔,dependencies是用來配置腳本的依賴的。而dependencies最終又是委托到了DependencyHandler。
看到了Gradles是多么廣泛的使用委托了吧。理解委托是很重要滴。
Script blocks
默認(rèn)情況下,Project中預(yù)先定義了很多script block,但是Gradle插件允許我們自己定義新的script blocks!
這就意味著,如果你在build腳本頂層發(fā)了一些{…},但是你在Gradle的文檔中卻找不到這個(gè)script blocks或者方法,絕大多情況下,這是一些來自插件中定義的script block。
android Script block
我們來看看默認(rèn)的Android app/build.gradle文件:
apply plugin: 'com.android.application' android { compileSdkVersion 22 buildToolsVersion "22.0.1" defaultConfig { applicationId "com.trickyandroid.testapp" minSdkVersion 16 targetSdkVersion 22 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } }
Task順序
我注意到我在使用Gradle的時(shí)候遇到的大多數(shù)問題都是和task的執(zhí)行順序有關(guān)的。很明顯如果我的構(gòu)建會工作的更好如果我的task都是在正確的時(shí)候執(zhí)行。下面我們就深入了解一下如何更改task的執(zhí)行順序。
dependsOn
我認(rèn)為最直接的方式來說明的你task的執(zhí)行時(shí)依賴別的task的方法就是使用dependsOn方法。
比如下面的場景,已經(jīng)存在task A,我們要添加一個(gè)task B,它的執(zhí)行必須要在A執(zhí)行完之后:
這是一個(gè)很簡單的場景,假定A和B的定義如下:
task A << {println 'Hello from A'} task B << {println 'Hello from B'}
只需要簡單的調(diào)用B.dependsOn A,就可以了。
這意味著,只要我執(zhí)行task B,task A都會先執(zhí)行。
paveldudka$ gradle B :A Hello from A :B Hello from B
另外,你也可以在task的配置區(qū)中來聲明它的依賴:
task A << {println 'Hello from A'} task B { dependsOn A doLast { println 'Hello from B' } }
如果我們想要在已經(jīng)存在的task依賴中插入我們的task該怎么做呢?
過程和剛才類似。假定已經(jīng)存在如下的task依賴:
task A << {println 'Hello from A'} task B << {println 'Hello from B'} task C << {println 'Hello from C'} B.dependsOn A C.dependsOn B
加入我們的新的task
task B1 << {println 'Hello from B1'} B1.dependsOn B C.dependsOn B1
輸出:
paveldudka$ gradle C :A Hello from A :B Hello from B :B1 Hello from B1 :C Hello from C
注意dependsOn把task添加到依賴的集合中,所以依賴多個(gè)task是沒有問題的。
task B1 << {println 'Hello from B1'} B1.dependsOn B B1.dependsOn Q
輸出:
paveldudka$ gradle B1 :A Hello from A :B Hello from B :Q Hello from Q :B1 Hello from B1
mustRunAfter
現(xiàn)在假定我又一個(gè)task,它依賴于其他兩個(gè)task。這里我使用一個(gè)真實(shí)的場景,我有兩個(gè)task,一個(gè)單元測試的task,一個(gè)是UI測試的task。另外還有一個(gè)task是跑所有的測試的,它依賴于前面的兩個(gè)task。
task unit << {println 'Hello from unit tests'} task ui << {println 'Hello from UI tests'} task tests << {println 'Hello from all tests!'} tests.dependsOn unit tests.dependsOn ui
輸出:
paveldudka$ gradle tests :ui Hello from UI tests :unit Hello from unit tests :tests Hello from all tests!
盡管unitest和UI test會子啊test task之前執(zhí)行,但是unit和ui這兩個(gè)task的執(zhí)行順序是不能保證的。雖然現(xiàn)在來看是按照字母表的順序執(zhí)行,但這是依賴于Gradle的實(shí)現(xiàn)的,你的代碼中絕對不能依賴這種順序。
由于UI測試時(shí)間遠(yuǎn)比unit test時(shí)間長,因此我希望unit test先執(zhí)行。一個(gè)解決辦法就是讓ui task依賴于unit task。
task unit << {println 'Hello from unit tests'} task ui << {println 'Hello from UI tests'} task tests << {println 'Hello from all tests!'} tests.dependsOn unit tests.dependsOn ui ui.dependsOn unit // <-- I added this dependency
輸出:
paveldudka$ gradle tests :unit Hello from unit tests :ui Hello from UI tests :tests Hello from all tests!
現(xiàn)在unit test會在ui test之前執(zhí)行了。
但是這里有個(gè)很惡心的問題,我的ui測試其實(shí)并不依賴于unit test。我希望能夠單獨(dú)的執(zhí)行ui test,但是這里每次我執(zhí)行ui test,都會先執(zhí)行unit test。
這里就要用到mustRunAfter了。mustRunAfter并不會添加依賴,它只是告訴Gradle執(zhí)行的優(yōu)先級如果兩個(gè)task同時(shí)存在。比如我們這里就可以指定ui.mustRunAfter unit,這樣如果ui task和unit task同時(shí)存在,Gradle會先執(zhí)行unit test,而如果只執(zhí)行g(shù)radle ui,并不會去執(zhí)行unit task。
task unit << {println 'Hello from unit tests'} task ui << {println 'Hello from UI tests'} task tests << {println 'Hello from all tests!'} tests.dependsOn unit tests.dependsOn ui ui.mustRunAfter unit
輸出:
paveldudka$ gradle tests :unit Hello from unit tests :ui Hello from UI tests :tests Hello from all tests!
依賴關(guān)系如下圖:
mustRunAfter在Gradle2.4中目前還是實(shí)驗(yàn)性的功能。
finalizedBy
現(xiàn)在我們已經(jīng)有兩個(gè)task,unit和ui,假定這兩個(gè)task都會輸出測試報(bào)告,現(xiàn)在我想把這兩個(gè)測試報(bào)告合并成一個(gè):
task unit << {println 'Hello from unit tests'} task ui << {println 'Hello from UI tests'} task tests << {println 'Hello from all tests!'} task mergeReports << {println 'Merging test reports'} tests.dependsOn unit tests.dependsOn ui ui.mustRunAfter unit mergeReports.dependsOn tests
現(xiàn)在如果我想獲得ui和unit的測試報(bào)告,執(zhí)行task mergeReports就可以了。
paveldudka$ gradle mergeReports :unit Hello from unit tests :ui Hello from UI tests :tests Hello from all tests! :mergeReports Merging test reports
這個(gè)task是能工作,但是看起來好笨啊。mergeReports從用戶的角度來看感覺不是特別好。我希望執(zhí)行tests task就可以獲得測試報(bào)告,而不必知道m(xù)ergeReports的存在。當(dāng)然我可以把merge的邏輯挪到tests task中,但我不想把tests task搞的太臃腫,我還是繼續(xù)把merge的邏輯放在mergeReports task中。
finalizeBy來救場了。顧名思義,finalizeBy就是在task執(zhí)行完之后要執(zhí)行的task。修改我們的腳本如下:
task unit << {println 'Hello from unit tests'} task ui << {println 'Hello from UI tests'} task tests << {println 'Hello from all tests!'} task mergeReports << {println 'Merging test reports'} tests.dependsOn unit tests.dependsOn ui ui.mustRunAfter unit mergeReports.dependsOn tests tests.finalizedBy mergeReports
現(xiàn)在執(zhí)行tests task就可以拿到測試報(bào)告了:
paveldudka$ gradle tests :unit Hello from unit tests :ui Hello from UI tests :tests Hello from all tests! :mergeReports Merging test reports
相關(guān)文章
Spring實(shí)戰(zhàn)之XML與JavaConfig的混合配置詳解
大家都知道Spring的顯示配置方式有兩種,一種是基于XML配置,一種是基于JavaConfig的方式配置。那么下這篇文章主要給大家分別介紹如何在JavaConfig中引用XML配置的bean以及如何在XML配置中引用JavaConfig,需要的朋友可以參考下。2017-07-07java基礎(chǔ)之接口組成更新的實(shí)現(xiàn)
本文主要介紹了java基礎(chǔ)之接口組成更新的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04Springboot實(shí)現(xiàn)對配置文件中的明文密碼加密詳解
我們在SpringBoot項(xiàng)目當(dāng)中,會把數(shù)據(jù)庫的用戶名密碼等配置直接放在yaml或者properties文件中,這樣維護(hù)數(shù)據(jù)庫的密碼等敏感信息顯然是有一定風(fēng)險(xiǎn)的。所以本文為大家整理了對配置文件中的明文密碼加密的方法,希望對大家有所幫助2023-03-03SpringBoot 普通類調(diào)用Bean對象的一種方式推薦
這篇文章主要介紹了SpringBoot 普通類調(diào)用Bean對象的一種方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11spring?boot教程之建立第一個(gè)HelloWorld
這篇文章主要介紹了spring?boot教程之建立第一個(gè)HelloWorld的相關(guān)資料,需要的朋友可以參考下2022-08-08Java實(shí)現(xiàn)視頻時(shí)間維度剪切的工具類
這篇文章主要為大家詳細(xì)介紹了將視頻按照時(shí)間維度進(jìn)行剪切的Java工具類,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下2022-12-12