建置 Java & JVM 專案
Gradle 使用慣例優於配置的方法來建置基於 JVM 的專案,這借鑒了 Apache Maven 的幾個慣例。特別是,它對原始檔和資源使用相同的預設目錄結構,並且可以與 Maven 相容的儲存庫一起使用。
在本章中,我們將詳細介紹 Java 專案,但大多數主題也適用於其他支援的 JVM 語言,例如 Kotlin、Groovy 和 Scala。如果您在透過 Gradle 建置基於 JVM 的專案方面沒有太多經驗,請查看 Java 範例,以獲取有關如何建置各種基本 Java 專案類型的逐步說明。
本節中的範例使用 Java 函式庫外掛。但是,描述的功能由所有 JVM 外掛共享。不同外掛的詳細資訊可在其專用文件中找到。 |
簡介
Java 專案最簡單的建置腳本是應用 Java 函式庫外掛,並可選擇性地設定專案版本並選擇要使用的 Java 工具鏈
plugins {
`java-library`
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
version = "1.2.1"
plugins {
id 'java-library'
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
version = '1.2.1'
透過應用 Java 函式庫外掛,您可以獲得一系列功能
-
compileJava
工作,用於編譯 src/main/java 下的所有 Java 原始檔 -
compileTestJava
工作,用於 src/test/java 下的原始檔 -
test
工作,用於執行來自 src/test/java 的測試 -
jar
工作,用於將main
編譯類別和來自 src/main/resources 的資源封裝到名為 <專案>-<版本>.jar 的單個 JAR 中 -
javadoc
工作,用於為main
類別產生 Javadoc
這不足以建置任何非平凡的 Java 專案 — 至少,您可能會有檔案相依性。但這表示您的建置腳本只需要特定於您的專案的資訊。
雖然範例中的屬性是可選的,但我們建議您在專案中指定它們。配置工具鏈可防止專案使用不同 Java 版本建置時出現問題。版本字串對於追蹤專案的進展非常重要。專案版本預設也用於封存檔名中。 |
Java 函式庫外掛也將上述工作整合到標準 基礎外掛生命週期工作 中
-
jar
已附加到assemble
-
test
已附加到check
本章的其餘部分說明了自訂建置以滿足您要求的不同途徑。您稍後還將看到如何調整函式庫、應用程式、Web 應用程式和企業應用程式的建置。
透過來源集宣告您的原始檔
Gradle 的 Java 支援首先引入了用於建置基於來源的專案的新概念:來源集。主要思想是原始檔和資源通常按類型邏輯分組,例如應用程式碼、單元測試和整合測試。每個邏輯組通常都有自己的檔案相依性、類別路徑等等。重要的是,構成來源集的檔案不必位於同一個目錄中!
來源集是一個強大的概念,它將編譯的幾個方面結合在一起
-
原始檔及其位置
-
編譯類別路徑,包括任何所需的相依性(透過 Gradle 配置)
-
編譯後的類別檔放置位置
您可以在此圖表中看到這些彼此之間的關係

陰影框表示來源集本身的屬性。最重要的是,Java 函式庫外掛會自動為您或外掛定義的每個來源集建立一個編譯工作 — 名為 compileSourceSetJava
— 以及多個 相依性配置。
main
來源集大多數語言外掛(包括 Java)都會自動建立一個名為 main
的來源集,用於專案的生產程式碼。此來源集很特別,因為其名稱未包含在配置和工作的名稱中,因此您只有 compileJava
工作以及 compileOnly
和 implementation
配置,而不是 compileMainJava
、mainCompileOnly
和 mainImplementation
。
Java 專案通常包含原始檔以外的資源,例如屬性檔案,這些檔案可能需要處理 — 例如,透過替換檔案中的符記 — 並封裝在最終 JAR 中。Java 函式庫外掛透過自動為每個定義的來源集建立一個名為 processSourceSetResources
的專用工作(或 main
來源集的 processResources
)來處理此問題。以下圖表顯示了來源集如何與此工作配合

與之前一樣,陰影框表示來源集的屬性,在本例中,來源集包含資源檔的位置以及它們複製到的位置。
除了 main
來源集之外,Java 函式庫外掛還定義了一個 test
來源集,表示專案的測試。此來源集由 test
工作使用,該工作執行測試。您可以在 Java 測試 章節中了解有關此工作和相關主題的更多資訊。
專案通常將此來源集用於單元測試,但如果您願意,也可以將其用於整合、驗收和其他類型的測試。另一種方法是為您的其他每種測試類型定義新的來源集,這通常是出於以下原因之一或兩個原因而完成的
-
您希望將測試彼此分開,以提高美觀性和可管理性
-
不同的測試類型需要不同的編譯或執行階段類別路徑,或設定中的其他一些差異
您可以在 Java 測試章節中看到此方法的範例,該章節向您展示了如何在專案中設定整合測試。
您將在以下內容中了解有關來源集及其提供的功能的更多資訊
來源集配置
建立來源集時,它也會建立許多如上所述的配置。建置邏輯不應嘗試建立或存取這些配置,直到它們首先由來源集建立。
建立來源集時,如果其中一個自動建立的配置已存在,Gradle 將發出棄用警告。如果現有配置的角色與來源集將分配的角色不同,則其角色將被變更為正確的值,並發出另一個棄用警告。
下面的建置示範了這種不必要的行為。
configurations {
val myCodeCompileClasspath: Configuration by creating
}
sourceSets {
val myCode: SourceSet by creating
}
configurations {
myCodeCompileClasspath
}
sourceSets {
myCode
}
在這種情況下,將發出以下棄用警告
When creating configurations during sourceSet custom setup, Gradle found that configuration customCompileClasspath already exists with permitted usage(s):
Consumable - this configuration can be selected by another project as a dependency
Resolvable - this configuration can be resolved by this project to a set of files
Declarable - this configuration can have dependencies added to it
Yet Gradle expected to create it with the usage(s):
Resolvable - this configuration can be resolved by this project to a set of files
遵循兩個簡單的最佳實務將避免此問題
-
不要建立名稱將由來源集使用的配置,例如以
Api
、Implementation
、ApiElements
、CompileOnly
、CompileOnlyApi
、RuntimeOnly
、RuntimeClasspath
或RuntimeElements
結尾的名稱。(此列表並非詳盡無遺。) -
在任何自訂配置之前建立任何自訂來源集。
請記住,每當您在 configurations
容器中引用配置時(無論是否提供初始化動作),Gradle 都會建立配置。有時,當使用 Groovy DSL 時,這種建立並不 очевид,如下例所示,其中 myCustomConfiguration
在呼叫 extendsFrom
之前建立。
configurations {
myCustomConfiguration.extendsFrom(implementation)
}
管理您的相依性
絕大多數 Java 專案都依賴函式庫,因此管理專案的相依性是建置 Java 專案的重要組成部分。相依性管理是一個很大的主題,因此我們將在此處重點介紹 Java 專案的基礎知識。如果您想深入了解詳細資訊,請查看 Gradle 中的相依性管理簡介。
為您的 Java 專案指定相依性僅需要三個資訊
-
您需要哪個相依性,例如名稱和版本
-
需要它的用途,例如編譯或執行
-
在哪裡尋找它
前兩個在 dependencies {}
區塊中指定,第三個在 repositories {}
區塊中指定。例如,若要告訴 Gradle 您的專案需要版本 3.6.7 的 Hibernate Core 來編譯和執行您的生產程式碼,並且您想要從 Maven Central 儲存庫下載函式庫,您可以使用以下片段
repositories {
mavenCentral()
}
dependencies {
implementation("org.hibernate:hibernate-core:3.6.7.Final")
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.hibernate:hibernate-core:3.6.7.Final'
}
Gradle 術語中,這三個元素如下
-
儲存庫(例如:
mavenCentral()
)— 在哪裡尋找您宣告為相依性的模組 -
配置(例如:
implementation
)— 相依性的命名集合,為特定目標(例如編譯或執行模組)分組在一起 — 更靈活的 Maven 範圍形式 -
模組座標(例如:
org.hibernate:hibernate-core-3.6.7.Final
)— 相依性的 ID,通常採用 '<群組>:<模組>:<版本>' 格式(或 Maven 術語中的 '<groupId>:<artifactId>:<version>')
您可以在此處找到更全面的相依性管理術語詞彙表。
就配置而言,主要的關注點是
-
compileOnly
— 用於編譯生產程式碼所必需但又不應成為執行階段類別路徑一部分的相依性 -
implementation
(取代compile
)— 用於編譯和執行階段 -
runtimeOnly
(取代runtime
)— 僅在執行階段使用,不用於編譯 -
testCompileOnly
— 與compileOnly
相同,只是它用於測試 -
testImplementation
—implementation
的測試等效項 -
testRuntimeOnly
—runtimeOnly
的測試等效項
您可以在 外掛參考章節中了解有關這些及其相互關係的更多資訊。
請注意,Java 函式庫外掛為編譯模組和任何依賴於它的模組所需的相依性提供兩個額外的配置 — api
和 compileOnlyApi
。
compile
配置?Java 函式庫外掛在歷史上一直使用 compile
配置來處理編譯和執行專案生產程式碼所需的相依性。它現在已棄用,並且在使用時會發出警告,因為它沒有區分影響 Java 函式庫專案公用 API 的相依性和不影響公用 API 的相依性。您可以在 建置 Java 函式庫中了解有關此區別重要性的更多資訊。
我們在這裡僅觸及了表面,因此我們建議您在熟悉使用 Gradle 建置 Java 專案的基礎知識後,閱讀專門的相依性管理章節。需要進一步閱讀的一些常見情境包括
您將發現 Gradle 具有用於處理相依性的豐富 API — 需要時間才能掌握,但對於常見情境來說使用起來很簡單。
編譯您的程式碼
如果您遵循慣例,編譯生產和測試程式碼都可能非常容易
-
將您的生產原始碼放在 src/main/java 目錄下
-
將您的測試原始碼放在 src/test/java 下
-
在
compileOnly
或implementation
配置中宣告您的生產編譯相依性(請參閱上一節) -
在
testCompileOnly
或testImplementation
配置中宣告您的測試編譯相依性 -
執行生產程式碼的
compileJava
工作和測試的compileTestJava
其他 JVM 語言外掛(例如 Groovy 的外掛)遵循相同的慣例模式。我們建議您盡可能遵循這些慣例,但您不必這樣做。您有幾個自訂選項,如下所述。
自訂檔案和目錄位置
假設您有一個舊版專案,該專案使用 src 目錄作為生產程式碼,並使用 test 作為測試程式碼。傳統的目錄結構將不起作用,因此您需要告訴 Gradle 在哪裡找到原始檔。您可以透過來源集配置來完成此操作。
每個來源集都定義了其原始碼所在的位置,以及資源和類別檔的輸出目錄。您可以使用以下語法覆寫慣例值
sourceSets {
main {
java {
setSrcDirs(listOf("src"))
}
}
test {
java {
setSrcDirs(listOf("test"))
}
}
}
sourceSets {
main {
java {
srcDirs = ['src']
}
}
test {
java {
srcDirs = ['test']
}
}
}
現在 Gradle 將僅直接在 src 和 test 中搜尋各自的原始碼。如果您不想覆寫慣例,而只是想新增一個額外的原始目錄,也許是包含一些您想要分開的第三方原始碼的目錄,該怎麼辦?語法類似
sourceSets {
main {
java {
srcDir("thirdParty/src/main/java")
}
}
}
sourceSets {
main {
java {
srcDir 'thirdParty/src/main/java'
}
}
}
至關重要的是,我們在這裡使用方法 srcDir()
來附加目錄路徑,而設定 srcDirs
屬性會取代任何現有值。這是 Gradle 中的常見慣例:設定屬性會取代值,而相應的方法會附加值。
您可以在 SourceSet 和 SourceDirectorySet 的 DSL 參考中查看來源集上可用的所有屬性和方法。請注意,srcDirs
和 srcDir()
都位於 SourceDirectorySet
上。
變更編譯器選項
大多數編譯器選項都可以透過相應的工作存取,例如 compileJava
和 compileTestJava
。這些工作的類型為 JavaCompile,因此請閱讀工作參考以獲取最新的完整選項列表。
例如,如果您想為編譯器使用單獨的 JVM 程序,並防止編譯失敗導致建置失敗,您可以使用此配置
tasks.compileJava {
options.isIncremental = true
options.isFork = true
options.isFailOnError = false
}
compileJava {
options.incremental = true
options.fork = true
options.failOnError = false
}
這也是您可以變更編譯器的詳細程度、停用位元組碼中的偵錯輸出以及配置編譯器可以在哪裡找到註解處理器的方式。
鎖定特定 Java 版本
預設情況下,Gradle 會將 Java 程式碼編譯為執行 Gradle 的 JVM 的語言級別。如果您需要在編譯時鎖定特定版本的 Java,Gradle 提供了多個選項
使用工具鏈
當使用特定工具鏈編譯 Java 程式碼時,實際編譯由指定 Java 版本的編譯器執行。編譯器提供對請求的 Java 語言版本的語言功能和 JDK API 的存取權。
在最簡單的情況下,可以使用 java
擴充功能為專案配置工具鏈。這樣,不僅編譯受益於它,而且其他工作(例如 test
和 javadoc
)也將始終如一地使用相同的工具鏈。
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
您可以在 Java 工具鏈指南中了解有關此內容的更多資訊。
使用 Java 發行版本
Gradle 支援從 Java 10 開始使用 release 標誌。可以在編譯工作上配置它,如下所示。
tasks.compileJava {
options.release = 7
}
compileJava {
options.release = 7
}
release 標誌提供的保證與工具鏈類似。它驗證 Java 來源未使用在較新 Java 版本中引入的語言功能,並且程式碼未存取來自較新 JDK 的 API。編譯器產生的位元組碼也對應於請求的 Java 版本,這表示編譯後的程式碼無法在較舊的 JVM 上執行。
Java 編譯器的 release
選項是在 Java 9 中引入的。但是,由於 Java 9 中的錯誤,只有從 Java 10 開始才可能將此選項與 Gradle 一起使用。
使用 Java 相容性選項
sourceCompatibility
和 targetCompatibility
選項對應於 Java 編譯器選項 -source
和 -target
。它們被認為是鎖定特定 Java 版本的舊版機制。但是,這些選項不能防止使用在較新 Java 版本中引入的 API。
sourceCompatibility
-
定義您的原始檔中使用的 Java 語言版本。
targetCompatibility
-
定義您的程式碼應在其上執行的最低 JVM 版本,即,它決定編譯器產生的位元組碼版本。
可以使用相同名稱的屬性,在每個 JavaCompile 工作或所有編譯工作的 java { }
擴充功能上設定這些選項。
鎖定 Java 6 和 Java 7
Gradle 本身只能在 Java 版本 8 或更高版本的 JVM 上執行。但是,Gradle 仍然支援編譯、測試、產生 Javadoc 和執行 Java 6 和 Java 7 的應用程式。不支援 Java 5 及更低版本。
如果使用 Java 10+,利用 release 標誌可能是一種更簡單的解決方案,請參閱上文。 |
若要使用 Java 6 或 Java 7,需要配置以下工作
-
JavaCompile
工作以分支並使用正確的 Java 首頁 -
Javadoc
工作以使用正確的javadoc
可執行檔 -
Test
和JavaExec
工作以使用正確的java
可執行檔。
透過使用 Java 工具鏈,可以按如下方式完成此操作
java {
toolchain {
languageVersion = JavaLanguageVersion.of(7)
}
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(7)
}
}
唯一的要求是已安裝 Java 7,並且必須位於 Gradle 可以自動偵測到的位置或 明確配置的位置。
分別編譯獨立來源
大多數專案至少有兩個獨立的來源集:生產程式碼和測試程式碼。Gradle 已經使這種情境成為其 Java 慣例的一部分,但是如果您有其他來源集呢?最常見的情境之一是當您有某種形式的獨立整合測試時。在這種情況下,自訂來源集可能正是您需要的。
您可以在 Java 測試章節中看到設定整合測試的完整範例。您可以設定以相同方式滿足不同角色的其他來源集。然後問題變為:何時應定義自訂來源集?
若要回答這個問題,請考慮來源是否
-
需要使用唯一的類別路徑進行編譯
-
產生的類別是否與
main
和test
類別的處理方式不同 -
構成專案的自然組成部分
如果您對問題 3 以及其他問題之一的回答皆為「是」,那麼自訂來源集可能是正確的方法。例如,整合測試通常是專案的一部分,因為它們測試 main
中的程式碼。此外,它們通常具有獨立於 test
來源集的自身相依性,或者需要使用自訂的 Test
任務來執行。
其他常見情境較不明確,可能會有更好的解決方案。例如
-
分離 API 和實作 JAR — 將這些作為個別專案可能更有意義,特別是如果您已經有多專案建置
-
產生的來源 — 如果產生的來源應該與生產程式碼一起編譯,請將其路徑新增至
main
來源集,並確保compileJava
任務依賴於產生來源的任務
如果您不確定是否要建立自訂來源集,請繼續建立。它應該很簡單明瞭,如果不是,那麼它可能不是適合這項工作的工具。
偵錯編譯錯誤
Gradle 為編譯失敗提供詳細的報告。
如果編譯任務失敗,錯誤摘要會顯示在以下位置
-
任務的輸出,為錯誤提供直接的上下文。
-
建置輸出底部的「發生錯誤」摘要,與所有其他失敗合併,以便於參考。
* What went wrong:
Execution failed for task ':project1:compileJava'.
> Compilation failed; see the compiler output below.
Java compilation warning
sample-project/src/main/java/Problem1.java:6: warning: [cast] redundant cast to String
var warning = (String)"warning";
^
Java compilation error
sample-project/src/main/java/Problem2.java:6: error: incompatible types: int cannot be converted to String
String a = 1;
^
此報告功能適用於 —continue
標記。
管理資源
許多 Java 專案除了原始碼檔案外,還會使用資源,例如圖片、組態檔和本地化資料。有時這些檔案只需要以未經修改的方式封裝,有時則需要作為範本檔案或其他方式進行處理。無論哪種方式,Java Library Plugin 都會為每個來源集新增特定的 Copy 任務,以處理其相關資源。
任務的名稱遵循 processSourceSetResources
的慣例 — 或 main
來源集的 processResources
— 它會自動將 src/[sourceSet]/resources 中的任何檔案複製到將包含在生產 JAR 中的目錄。此目標目錄也將包含在測試的執行階段類別路徑中。
由於 processResources
是 ProcessResources
任務的實例,您可以執行使用檔案章節中描述的任何處理。
Java 屬性檔案和可重現的建置
您可以透過 WriteProperties 任務輕鬆建立 Java 屬性檔案,這修正了 Properties.store()
的一個眾所周知的問題,該問題可能會降低增量建置的實用性。
用於寫入屬性檔案的標準 Java API 每次都會產生唯一的檔案,即使使用相同的屬性和值也是如此,因為它在註解中包含時間戳記。如果沒有任何屬性變更,Gradle 的 WriteProperties
任務會產生完全相同的逐位元組輸出。這是透過對屬性檔案的產生方式進行一些調整來實現的
-
輸出中未新增時間戳記註解
-
行分隔符號與系統無關,但可以明確配置(預設為
'\n'
) -
屬性按字母順序排序
有時,可能希望在不同的機器上以逐位元組的方式重新建立封存檔。您要確保從原始碼建置成品會產生相同的結果,無論何時何地建置,都是逐位元組相同的。這對於像 reproducible-builds.org 這樣的專案是必要的。
這些調整不僅能更好地整合增量建置,還有助於可重現的建置。本質上,可重現的建置保證您將從建置執行中看到相同的結果 — 包括測試結果和生產二進制檔案 — 無論您何時或在何種系統上執行它。
執行測試
除了在 src/test/java 中提供單元測試的自動編譯之外,Java Library Plugin 還原生支援執行使用 JUnit 3、4 和 5 的測試(JUnit 5 支援在 Gradle 4.6 中推出)和 TestNG。您會獲得
-
類型為 Test 的自動
test
任務,使用test
來源集 -
HTML 測試報告,其中包含來自所有執行的
Test
任務的結果 -
輕鬆篩選要執行的測試
-
精細控制測試的執行方式
-
建立您自己的測試執行和測試報告任務的機會
您不會為您宣告的每個來源集取得 Test
任務,因為並非每個來源集都代表測試!這就是為什麼您通常需要為整合和驗收測試等項目建立您自己的 Test
任務,如果它們無法包含在 test
來源集中。
由於測試方面有很多內容需要涵蓋,因此該主題在其自有章節中進行了探討,我們將在其中查看
-
測試的執行方式
-
如何透過篩選執行測試子集
-
Gradle 如何探索測試
-
如何配置測試報告並新增您自己的報告任務
-
如何利用特定的 JUnit 和 TestNG 功能
您也可以在 Test 的 DSL 參考中瞭解有關配置測試的更多資訊。
封裝和發佈
您如何封裝和可能發佈您的 Java 專案取決於它是什麼類型的專案。程式庫、應用程式、Web 應用程式和企業應用程式都有不同的需求。在本節中,我們將重點介紹 Java Library Plugin 提供的最基本功能。
預設情況下,Java Library Plugin 提供 jar
任務,該任務將所有編譯的生產類別和資源封裝到單個 JAR 中。此 JAR 也會由 assemble
任務自動建置。此外,如果需要,可以配置外掛程式以提供 javadocJar
和 sourcesJar
任務來封裝 Javadoc 和原始碼。如果使用發佈外掛程式,這些任務將在發佈期間自動執行,或可以直接呼叫。
java {
withJavadocJar()
withSourcesJar()
}
java {
withJavadocJar()
withSourcesJar()
}
如果您想建立「uber」(又名「fat」)JAR,則可以使用如下的任務定義
plugins {
java
}
version = "1.0.0"
repositories {
mavenCentral()
}
dependencies {
implementation("commons-io:commons-io:2.6")
}
tasks.register<Jar>("uberJar") {
archiveClassifier = "uber"
from(sourceSets.main.get().output)
dependsOn(configurations.runtimeClasspath)
from({
configurations.runtimeClasspath.get().filter { it.name.endsWith("jar") }.map { zipTree(it) }
})
}
plugins {
id 'java'
}
version = '1.0.0'
repositories {
mavenCentral()
}
dependencies {
implementation 'commons-io:commons-io:2.6'
}
tasks.register('uberJar', Jar) {
archiveClassifier = 'uber'
from sourceSets.main.output
dependsOn configurations.runtimeClasspath
from {
configurations.runtimeClasspath.findAll { it.name.endsWith('jar') }.collect { zipTree(it) }
}
}
有關可用的配置選項的更多詳細資訊,請參閱 Jar。請注意,您需要在此處使用 archiveClassifier
而不是 archiveAppendix
才能正確發佈 JAR。
您可以使用發佈外掛程式之一來發佈 Java 專案建立的 JAR
修改 JAR 資訊清單
Jar
、War
和 Ear
任務的每個實例都有一個 manifest
屬性,允許您自訂進入相應封存檔的 MANIFEST.MF 檔案。以下範例示範如何在 JAR 的資訊清單中設定屬性
tasks.jar {
manifest {
attributes(
"Implementation-Title" to "Gradle",
"Implementation-Version" to archiveVersion
)
}
}
jar {
manifest {
attributes("Implementation-Title": "Gradle",
"Implementation-Version": archiveVersion)
}
}
有關其提供的配置選項,請參閱 Manifest。
您也可以建立 Manifest
的獨立實例。這樣做的一個原因是為了在 JAR 之間共用資訊清單資訊。以下範例示範如何在 JAR 之間共用通用屬性
val sharedManifest = java.manifest {
attributes (
"Implementation-Title" to "Gradle",
"Implementation-Version" to version
)
}
tasks.register<Jar>("fooJar") {
manifest = java.manifest {
from(sharedManifest)
}
}
def sharedManifest = java.manifest {
attributes("Implementation-Title": "Gradle",
"Implementation-Version": version)
}
tasks.register('fooJar', Jar) {
manifest = java.manifest {
from sharedManifest
}
}
您可以使用的另一個選項是將資訊清單合併到單個 Manifest
物件中。這些來源資訊清單可以採用文字形式或其他 Manifest
物件的形式。在以下範例中,來源資訊清單都是文字檔案,除了 sharedManifest
,它是前一個範例中的 Manifest
物件
tasks.register<Jar>("barJar") {
manifest {
attributes("key1" to "value1")
from(sharedManifest, "src/config/basemanifest.txt")
from(listOf("src/config/javabasemanifest.txt", "src/config/libbasemanifest.txt")) {
eachEntry(Action<ManifestMergeDetails> {
if (baseValue != mergeValue) {
value = baseValue
}
if (key == "foo") {
exclude()
}
})
}
}
}
tasks.register('barJar', Jar) {
manifest {
attributes key1: 'value1'
from sharedManifest, 'src/config/basemanifest.txt'
from(['src/config/javabasemanifest.txt', 'src/config/libbasemanifest.txt']) {
eachEntry { details ->
if (details.baseValue != details.mergeValue) {
details.value = baseValue
}
if (details.key == 'foo') {
details.exclude()
}
}
}
}
}
資訊清單會按照它們在 from
語句中宣告的順序合併。如果基本資訊清單和合併的資訊清單都為相同的鍵定義了值,則預設情況下,合併的資訊清單獲勝。您可以透過新增 eachEntry
動作來完全自訂合併行為,在其中您可以存取每個結果資訊清單條目的 ManifestMergeDetails 實例。請注意,合併是延遲完成的,無論是在產生 JAR 時,還是在呼叫 Manifest.writeTo()
或 Manifest.getEffectiveManifest()
時。
說到 writeTo()
,您可以隨時使用它輕鬆地將資訊清單寫入磁碟,如下所示
tasks.jar { manifest.writeTo(layout.buildDirectory.file("mymanifest.mf")) }
tasks.named('jar') { manifest.writeTo(layout.buildDirectory.file('mymanifest.mf')) }
產生 API 文件
Java Library Plugin 提供類型為 Javadoc 的 javadoc
任務,它將為您的所有生產程式碼產生標準 Javadoc,即 main
來源集中的任何來源。該任務支援 Javadoc 參考文件中描述的核心 Javadoc 和標準 doclet 選項。有關這些選項的完整列表,請參閱 CoreJavadocOptions 和 StandardJavadocDocletOptions。
作為您可以做什麼的一個範例,假設您想在 Javadoc 註解中使用 Asciidoc 語法。為此,您需要將 Asciidoclet 新增到 Javadoc 的 doclet 路徑。以下範例正是這樣做的
val asciidoclet by configurations.creating
dependencies {
asciidoclet("org.asciidoctor:asciidoclet:1.+")
}
tasks.register("configureJavadoc") {
doLast {
tasks.javadoc {
options.doclet = "org.asciidoctor.Asciidoclet"
options.docletpath = asciidoclet.files.toList()
}
}
}
tasks.javadoc {
dependsOn("configureJavadoc")
}
configurations {
asciidoclet
}
dependencies {
asciidoclet 'org.asciidoctor:asciidoclet:1.+'
}
tasks.register('configureJavadoc') {
doLast {
javadoc {
options.doclet = 'org.asciidoctor.Asciidoclet'
options.docletpath = configurations.asciidoclet.files.toList()
}
}
}
javadoc {
dependsOn configureJavadoc
}
您不必為此建立配置,但這是一種優雅的方式來處理獨特用途所需的相依性。
您可能還想建立自己的 Javadoc 任務,例如為測試產生 API 文件
tasks.register<Javadoc>("testJavadoc") {
source = sourceSets.test.get().allJava
}
tasks.register('testJavadoc', Javadoc) {
source = sourceSets.test.allJava
}
這些只是您可能會遇到的兩個非平凡但常見的自訂。
清理建置
Java Library Plugin 透過應用 Base Plugin,為您的專案新增了 clean
任務。此任務只是刪除 layout.buildDirectory
目錄中的所有內容,因此您應該始終將建置產生的檔案放在那裡。該任務是 Delete 的實例,您可以透過設定其 dir
屬性來變更它刪除的目錄。
建置 JVM 組件
所有特定的 JVM 外掛程式都建立在 Java Plugin 之上。上面的範例僅說明了此基本外掛程式提供的概念,並與所有 JVM 外掛程式共用。
繼續閱讀以瞭解哪些外掛程式適合哪種類型的專案,因為建議選擇特定的外掛程式,而不是直接應用 Java Plugin。
建置 Java 程式庫
程式庫專案的獨特之處在於它們被其他 Java 專案使用(或「消耗」)。這意味著與 JAR 檔案一起發佈的相依性中繼資料 — 通常以 Maven POM 的形式 — 至關重要。特別是,您的程式庫的使用者應該能夠區分兩種不同類型的相依性:僅編譯您的程式庫所需的相依性和編譯使用者也需要的相依性。
Gradle 透過 Java Library Plugin 管理這種區別,該外掛程式除了本章涵蓋的 implementation 配置外,還引入了 api 配置。如果來自相依性的類型出現在您的程式庫公共類別的公共欄位或方法中,則該相依性會透過您程式庫的公共 API 公開,因此應將其新增至 api 配置。否則,該相依性是內部實作細節,應新增至 implementation。
如果您不確定 API 和實作相依性之間的區別,Java Library Plugin 章節有詳細的說明。此外,您可以瀏覽建置 Java 程式庫的基本實用範例。
建置 Java 應用程式
封裝為 JAR 的 Java 應用程式未設定為可從命令列或桌面環境輕鬆啟動。Application Plugin 透過建立包含生產 JAR、其相依性和 Unix 類和 Windows 系統的啟動腳本的發行版來解決命令列方面的問題。
有關更多詳細資訊,請參閱外掛程式的章節,但以下是您獲得內容的快速摘要
-
assemble
建立應用程式的 ZIP 和 TAR 發行版,其中包含執行它所需的一切 -
run
任務,從建置啟動應用程式(用於輕鬆測試) -
Shell 和 Windows Batch 腳本,用於啟動應用程式
您可以在相應的範例中看到建置 Java 應用程式的基本範例。
建置 Java Web 應用程式
Java Web 應用程式可以透過多種方式封裝和部署,具體取決於您使用的技術。例如,您可以使用Spring Boot 和 fat JAR 或在 Netty 上執行的Reactive 系統。無論您使用何種技術,Gradle 及其龐大的外掛程式社群都將滿足您的需求。但是,核心 Gradle 僅直接支援部署為 WAR 檔案的傳統基於 Servlet 的 Web 應用程式。
該支援透過 War Plugin 提供,它會自動應用 Java Plugin 並新增額外的封裝步驟,執行以下操作
-
將靜態資源從 src/main/webapp 複製到 WAR 的根目錄
-
將編譯的生產類別複製到 WAR 的 WEB-INF/classes 子目錄
-
將程式庫相依性複製到 WAR 的 WEB-INF/lib 子目錄
這是由 war
任務完成的,它有效地取代了 jar
任務 — 儘管該任務仍然存在 — 並且附加到 assemble
生命周期任務。有關更多詳細資訊和配置選項,請參閱外掛程式的章節。
核心不支援直接從建置執行您的 Web 應用程式,但我們建議您嘗試 Gretty 社群外掛程式,它提供嵌入式 Servlet 容器。
建置 Java EE 應用程式
Java 企業系統多年來發生了很大變化,但如果您仍在部署到 JEE 應用程式伺服器,則可以使用 Ear Plugin。這會新增用於建置 EAR 檔案的慣例和任務。外掛程式的章節有更多詳細資訊。
建置 Java 平台
Java 平台代表一組相依性宣告和約束,它們形成一個有凝聚力的單元,應用於消耗專案。平台沒有來源,也沒有自己的成品。它在 Maven 世界中對應於 BOM。
該支援透過 Java Platform plugin 提供,它設定了不同的配置和發佈組件。
此外掛程式是例外,因為它不應用 Java Plugin。 |
啟用 Java 預覽功能
使用 Java 預覽功能很可能會使您的程式碼與未經功能預覽編譯的程式碼不相容。因此,我們強烈建議您不要發佈使用預覽功能編譯的程式庫,並將功能預覽的使用限制在玩具專案中。 |
若要為編譯、測試執行和執行階段啟用 Java 預覽功能,您可以使用以下 DSL 代码片段
tasks.withType<JavaCompile>().configureEach {
options.compilerArgs.add("--enable-preview")
}
tasks.withType<Test>().configureEach {
jvmArgs("--enable-preview")
}
tasks.withType<JavaExec>().configureEach {
jvmArgs("--enable-preview")
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs += "--enable-preview"
}
tasks.withType(Test).configureEach {
jvmArgs += "--enable-preview"
}
tasks.withType(JavaExec).configureEach {
jvmArgs += "--enable-preview"
}
建置其他 JVM 語言專案
如果您想利用 JVM 的多語言方面,此處描述的大部分內容仍然適用。
語言之間的編譯相依性
這些外掛程式在 Groovy/Scala 編譯和 Java 編譯之間建立相依性(來源集 java
資料夾中的原始碼)。您可以透過調整所涉及的編譯任務的類別路徑來變更此預設行為,如下例所示
tasks.named<AbstractCompile>("compileGroovy") {
// Groovy only needs the declared dependencies
// (and not longer the output of compileJava)
classpath = sourceSets.main.get().compileClasspath
}
tasks.named<AbstractCompile>("compileJava") {
// Java also depends on the result of Groovy compilation
// (which automatically makes it depend of compileGroovy)
classpath += files(sourceSets.main.get().groovy.classesDirectory)
}
tasks.named('compileGroovy') {
// Groovy only needs the declared dependencies
// (and not longer the output of compileJava)
classpath = sourceSets.main.compileClasspath
}
tasks.named('compileJava') {
// Java also depends on the result of Groovy compilation
// (which automatically makes it depend of compileGroovy)
classpath += files(sourceSets.main.groovy.classesDirectory)
}
-
透過將
compileGroovy
類別路徑設定為僅為sourceSets.main.compileClasspath
,我們有效地移除了先前由類別路徑也考慮到sourceSets.main.java.classesDirectory
而宣告的對compileJava
的相依性 -
透過將
sourceSets.main.groovy.classesDirectory
新增到compileJava
classpath
,我們有效地宣告了對compileGroovy
任務的相依性
所有這些都可以透過使用目錄屬性來實現。