Gradle jvm插件系列教程之Java?Library插件權威詳解
【Gradle jvm插件系列4】 Java Library插件用法示例權威詳解
使用方法
要使用Java Library插件,請在構建腳本中包含以下內(nèi)容:
plugins { id 'java-library' }
API和實現(xiàn)分離
標準Java插件和Java Library插件之間的關鍵區(qū)別在于后者引入了向消費者公開的API概念。Java庫是供其他組件消費的Java組件。在多項目構建中這是非常常見的用例,也適用于外部依賴。
該插件公開了兩個配置,用于聲明依賴項:api和implementation。api配置應該用于聲明由庫API導出的依賴項,而implementation配置應該用于聲明組件內(nèi)部的依賴項。
示例2:聲明API和實現(xiàn)依賴項
dependencies { api 'org.apache.httpcomponents:httpclient:4.5.7' implementation 'org.apache.commons:commons-lang3:3.5' }
在api配置中聲明的依賴項將傳遞給庫的消費者,因此將出現(xiàn)在消費者的編譯類路徑上。而在implementation配置中聲明的依賴項則不會向消費者公開,因此也不會出現(xiàn)在消費者的編譯類路徑上。這樣做有以下幾個好處:
- 依賴項不會意外泄露到消費者的編譯類路徑上,因此您永遠不會意外依賴于傳遞性依賴項。
- 編譯速度更快,因為類路徑更小。
- 當實現(xiàn)依賴項發(fā)生變化時,重新編譯次數(shù)較少:消費者無需重新編譯。
- 發(fā)布更干凈:與新的maven-publish插件一起使用時,Java庫會生成POM文件,明確區(qū)分針對庫的編譯所需和運行時所需(換句話說,不要混淆用于編譯庫本身和用于針對庫編譯的內(nèi)容)。
Gradle 7.0版本已刪除了compile和runtime配置,請參考升級指南以了解如何遷移到implementation和api配置。
如果構建使用具有POM元數(shù)據(jù)的發(fā)布模塊,則Java和Java Library插件都通過POM中使用的作用域來支持API和實現(xiàn)分離。這意味著編譯類路徑僅包括Maven compile范圍的依賴項,而運行時類路徑還包括Maven runtime范圍的依賴項。
這通常對于使用Maven發(fā)布的模塊沒有影響,因為定義項目的POM文件直接作為元數(shù)據(jù)發(fā)布。在這種情況下,編譯范圍包括既用于編譯項目的依賴項(即實現(xiàn)依賴項),也用于針對已發(fā)布庫進行編譯的依賴項(即API依賴項)。對于大多數(shù)已發(fā)布的庫來說,這意味著所有依賴項都屬于編譯范圍。如果您在現(xiàn)有庫中遇到此類問題,請考慮使用組件元數(shù)據(jù)規(guī)則來修復構建中的錯誤元數(shù)據(jù)。但是,如上所述,如果使用Gradle發(fā)布庫,則生成的POM文件將api依賴項放入compile范圍,將其余的implementation依賴項放入runtime范圍。
如果構建使用具有Ivy元數(shù)據(jù)的模塊,并且所有模塊都遵循特定結構,則可以按照此處描述的方式激活api和implementation分離。
從Gradle 5.0版本開始,默認情況下啟用了模塊的編譯和運行時范圍分離。從Gradle 4.6版本開始,您需要通過在settings.gradle中添加enableFeaturePreview('IMPROVED_POM_SUPPORT')
來激活它。
識別API和實現(xiàn)依賴項
本節(jié)將幫助您使用一些簡單的經(jīng)驗法則來識別代碼中的API和實現(xiàn)依賴項。首先的法則是:
在可能的情況下,優(yōu)先使用implementation配置。
這樣可以將依賴項保持在消費者的編譯類路徑之外。此外,如果任何實現(xiàn)類型意外泄漏到公共API中,消費者將立即無法編譯。
那么什么時候應該使用api配置呢?API依賴項是至少包含一個在庫二進制接口(ABI)中公開的類型的依賴項。這包括但不限于以下內(nèi)容:
- 用于超類或接口的類型
- 用于公共方法參數(shù)的類型,包括泛型參數(shù)類型(其中“公共”是對編譯器可見的內(nèi)容。即Java世界中的public、protected和package-private成員)
- 用于公共字段的類型
- 公共注解類型
相反,在以下列表中使用的任何類型都與ABI無關,因此應將其聲明為實現(xiàn)依賴項:
- 僅在方法體中使用的類型
- 僅在私有成員中使用的類型
- 僅在內(nèi)部類中找到的類型(Gradle的未來版本將允許您聲明哪些包屬于公共API)
下面的示例代碼使用了一些第三方庫,其中一個庫在類的公共API中暴露,另一個庫只在內(nèi)部使用。import語句無法幫助我們確定哪個是API依賴項,因此我們必須查看字段、構造函數(shù)和方法:
// The following types can appear anywhere in the code // but say nothing about API or implementation usage import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; public class HttpClientWrapper { private final HttpClient client; // private member: implementation details // HttpClient is used as a parameter of a public method // so "leaks" into the public API of this component public HttpClientWrapper(HttpClient client) { this.client = client; } // public methods belongs to your API public byte[] doRawGet(String url) { HttpGet request = new HttpGet(url); try { HttpEntity entity = doGet(request); ByteArrayOutputStream baos = new ByteArrayOutputStream(); entity.writeTo(baos); return baos.toByteArray(); } catch (Exception e) { ExceptionUtils.rethrow(e); // this dependency is internal only } finally { request.releaseConnection(); } return null; } // HttpGet and HttpEntity are used in a private method, so they don't belong to the API private HttpEntity doGet(HttpGet get) throws Exception { HttpResponse response = client.execute(get); if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { System.err.println("Method failed: " + response.getStatusLine()); } return response.getEntity(); } }
HttpClientWrapper的公共構造函數(shù)使用HttpClient作為參數(shù),因此它對消費者可見,因此屬于API。請注意,HttpGet和HttpEntity在私有方法的簽名中使用,因此它們不計入使HttpClient成為API依賴項。
另一方面,來自commons-lang庫的ExceptionUtils類型僅在方法體中使用(而不是在其簽名中),因此它是實現(xiàn)依賴項。
因此,我們可以推斷httpclient是一個API依賴項,而commons-lang是一個實現(xiàn)依賴項。這個結論可以轉(zhuǎn)化為構建腳本中的以下聲明:
dependencies { api 'org.apache.httpcomponents:httpclient:4.5.7' implementation 'org.apache.commons:commons-lang3:3.5' }
Java Library插件配置
下面的圖表描述了在使用Java Library插件時如何設置配置。
綠色配置是用戶應該用于聲明依賴關系的配置。
粉色配置是組件在編譯或與庫運行時使用的配置。
藍色配置是組件內(nèi)部使用的配置,僅供其自身使用。
下一個圖表描述了測試配置的設置:
下表描述了每個配置的作用:
表格1. Java Library插件 - 用于聲明依賴關系的配置
配置名稱 | 作用 | 可消耗性 | 可解析性 | 描述 |
---|---|---|---|---|
api | 聲明API依賴關系 | 否 | 否 | 在這里聲明傳遞導出到消費者的依賴關系,用于編譯時和運行時。 |
implementation | 聲明實現(xiàn)依賴關系 | 否 | 否 | 在這里聲明純粹為內(nèi)部使用而不打算向消費者公開的依賴關系(在運行時仍然對消費者公開)。 |
compileOnly | 聲明僅編譯依賴關系 | 否 | 否 | 在這里聲明在編譯時需要但在運行時不需要的依賴關系。這通常包括在運行時找到時會被屏蔽的依賴關系。 |
compileOnlyApi | 聲明僅編譯API依賴關系 | 否 | 否 | 在這里聲明模塊和消費者在編譯時需要但在運行時不需要的依賴關系。這通常包括在運行時找到時會被屏蔽的依賴關系。 |
runtimeOnly | 聲明運行時依賴關系 | 否 | 否 | 在這里聲明僅在運行時需要而不在編譯時需要的依賴關系。 |
testImplementation | 測試依賴關系 | 否 | 否 | 在這里聲明用于編譯測試的依賴關系。 |
testCompileOnly | 聲明僅測試編譯依賴關系 | 否 | 否 | 在這里聲明僅在測試編譯時需要但不應泄露到運行時的依賴關系。這通常包括在運行時找到時會被屏蔽的依賴關系。 |
testRuntimeOnly | 聲明測試運行時依賴關系 | 否 | 否 | 在這里聲明僅在測試運行時需要而不在測試編譯時需要的依賴關系。 |
表格2. Java Library插件 - 消費者使用的配置
配置名稱 | 作用 | 可消耗性 | 可解析性 | 描述 |
---|---|---|---|---|
apiElements | 用于編譯此庫 | 是 | 否 | 此配置用于供消費者檢索編譯此庫所需的所有元素。 |
runtimeElements | 用于執(zhí)行此庫 | 是 | 否 | 此配置用于供消費者檢索運行此庫所需的所有元素。 |
表格3. Java Library插件 - 庫本身使用的配置
配置名稱 | 作用 | 可消耗性 | 可解析性 | 描述 |
---|---|---|---|---|
compileClasspath | 用于編譯此庫 | 否 | 是 | 此配置包含此庫的編譯類路徑,因此在調(diào)用Java編譯器進行編譯時使用。 |
runtimeClasspath | 用于執(zhí)行此庫 | 否 | 是 | 此配置包含此庫的運行時類路徑。 |
testCompileClasspath | 用于編譯此庫的測試 | 否 | 是 | 此配置包含此庫的測試編譯類路徑。 |
testRuntimeClasspath | 用于執(zhí)行此庫的測試 | 否 | 是 | 此配置包含此庫的測試運行時類路徑。 |
為Java模塊系統(tǒng)構建模塊
自Java 9以來,Java本身提供了一個模塊系統(tǒng),允許在編譯和運行時進行嚴格的封裝。您可以通過在main/java源文件夾中創(chuàng)建一個module-info.java文件將Java庫轉(zhuǎn)換為Java模塊。
src └── main └── java └── module-info.java
在module info文件中,您聲明一個模塊名稱,您希望導出哪些模塊包,并且您需要哪些其他模塊。
// module-info.java file module org.gradle.sample { requires com.google.gson; // real module requires org.apache.commons.lang3; // automatic module // commons-cli-1.4.jar is not a module and cannot be required }
為了告訴Java編譯器一個Jar是一個模塊,而不是傳統(tǒng)的Java庫,Gradle需要將其放置在所謂的模塊路徑上。這是與classpath相反的一種選擇,classpath是告訴編譯器關于已編譯依賴關系的傳統(tǒng)方式。如果以下三個條件為真,Gradle將自動將您的依賴項的Jar放置在模塊路徑上,而不是在classpath上:
java.modularity.inferModulePath
沒有被關閉- 我們實際上正在構建一個模塊(而不是傳統(tǒng)的庫),這是通過添加
module-info.java
文件來表達的。 (另一種選擇是根據(jù)后面描述的Automatic-Module-Name Jar清單屬性添加。) - 我們的模塊依賴于的Jar本身是一個模塊,Gradle根據(jù)Jar中是否存在module-info.class(模塊描述符的編譯版本)來決定。 (或者,Jar清單中存在Automatic-Module-Name屬性)
接下來,介紹一些關于定義Java模塊和與Gradle的依賴管理交互的更多詳細信息。您還可以查看一個現(xiàn)成的示例來直接嘗試Java模塊支持。
聲明模塊依賴關系
在構建文件中聲明的依賴關系和在module-info.java文件中聲明的模塊依賴關系之間存在直接關系。理想情況下,這些聲明應該保持同步,如下表所示:
Java模塊指令 | Gradle配置 | 目的 |
---|---|---|
requires | implementation | 聲明實現(xiàn)依賴關系 |
requires transitive | api | 聲明API依賴關系 |
requires static | compileOnly | 聲明僅編譯依賴關系 |
requires static transitive | compileOnlyApi | 聲明僅編譯API依賴關系 |
目前,Gradle不會自動檢查依賴關系的聲明是否同步。這可能會在未來的版本中添加。
有關聲明模塊依賴關系的更多詳細信息,請參閱Java模塊系統(tǒng)的文檔。
聲明包可見性和服務
Java模塊系統(tǒng)支持比Gradle本身目前支持的更精細的封裝概念。例如,您需要明確聲明哪些包屬于您的API,哪些包只在模塊內(nèi)部可見。Gradle未來的版本可能會添加其中一些功能?,F(xiàn)在,請參閱Java模塊系統(tǒng)的文檔,了解如何在Java模塊中使用這些特性。
聲明模塊版本
Java模塊也有一個版本,它作為module-info.class文件中模塊標識的一部分進行編碼。當模塊運行時,可以檢查此版本。
// 示例:在構建腳本中聲明模塊版本或直接作為編譯任務選項 build.gradle version = '1.2' tasks.named('compileJava') { // 使用項目的版本或直接定義一個版本 options.javaModuleVersion = provider { version } }
使用非模塊化的庫
您可能希望在模塊化的Java項目中使用外部庫,例如Maven Central中的OSS庫。一些庫在其較新版本中已經(jīng)是具有模塊描述符的完整模塊。例如,com.google.code.gson:gson:2.8.9具有模塊名稱com.google.gson。
其他庫,例如org.apache.commons:commons-lang3:3.10,可能沒有提供完整的模塊描述符,但至少會在其清單文件中包含一個Automatic-Module-Name條目來定義模塊的名稱(示例中為org.apache.commons.lang3)。這樣的模塊,只有一個模塊名稱作為模塊描述,被稱為自動模塊,它導出所有其包并可以讀取模塊路徑上的所有模塊。
第三種情況是不提供任何模塊信息的傳統(tǒng)庫,例如commons-cli:commons-cli:1.4。Gradle將此類庫放置在類路徑上而不是模塊路徑上。對于Java來說,類路徑被視為一個模塊(稱為未命名模塊)。
// 示例:構建文件中聲明的模塊和庫的依賴關系 build.gradle dependencies { implementation 'com.google.code.gson:gson:2.8.9' // real module implementation 'org.apache.commons:commons-lang3:3.10' // automatic module implementation 'commons-cli:commons-cli:1.4' // plain library }
// 在module-info.java文件中聲明的模塊依賴關系 module org.gradle.sample.lib { requires com.google.gson; // real module requires org.apache.commons.lang3; // automatic module // commons-cli-1.4.jar is not a module and cannot be required }
雖然真正的模塊不能直接依賴于未命名模塊(只能通過添加命令行標志),但自動模塊也可以看到未命名模塊。因此,如果您無法避免依賴于沒有模塊信息的庫,您可以將該庫包裝在一個自動模塊中作為項目的一部分。如何執(zhí)行這個操作在下一節(jié)中描述。
處理非模塊化的方式之一是使用artifact transforms自己向現(xiàn)有的Jars添加模塊描述符。該示例包含一個小的buildSrc插件,用于注冊這樣的轉(zhuǎn)換器,您可以使用并根據(jù)需要進行調(diào)整。如果您想構建一個完全模塊化的應用程序,并希望Java運行時將所有內(nèi)容視為真正的模塊,則可能會對此感興趣。
禁用Java模塊支持
在極少數(shù)情況下,您可能希望禁用內(nèi)置的Java模塊支持,并通過其他方式定義模塊路徑。為了實現(xiàn)這一點,您可以禁用自動將任何Jar放置在模塊路徑上的功能。然后,即使在源集中具有module-info.java,Gradle也會將帶有模塊信息的Jars放置在類路徑上。這對應于Gradle版本<7.0的行為。
要使其工作,您需要在Java擴展上(對于所有任務)或個別任務上設置 modularity.inferModulePath = false
。
// 示例:禁用Gradle的模塊路徑推斷 build.gradle java { modularity.inferModulePath = false } tasks.named('compileJava') { modularity.inferModulePath = false }
構建自動模塊
如果可以的話,您應該始終為您的模塊編寫完整的module-info.java描述符。但是,有一些情況下,您可能考慮(最初)只為自動模塊提供模塊名稱:
- 您正在開發(fā)一個不是模塊的庫,但是您希望在下一個版本中將其用作模塊。添加Automatic-Module-Name是一個很好的第一步(Maven中央的大多數(shù)熱門OSS庫現(xiàn)在已經(jīng)這樣做了)。
- 如前一節(jié)所討論的,自動模塊可以用作真正模塊和類路徑上的傳統(tǒng)庫之間的適配器。
要將普通Java項目轉(zhuǎn)換為自動模塊,只需添加具有模塊名稱的清單條目:
// 示例:在Jar清單屬性中聲明自動模塊名稱 build.gradle tasks.named('jar') { manifest { attributes('Automatic-Module-Name': 'org.gradle.sample') } }
您可以將自動模塊定義為多項目的一部分,該項目還定義了真正的模塊(例如,作為與另一個庫的適配器)。盡管Gradle構建中的這種方式運行良好,但IDEA / Eclipse當前無法正確識別此類自動模塊項目。您可以通過在IDE的UI中手動將為自動模塊構建的Jar添加到無法找到它的項目的依賴項中來解決此問題。
使用類而不是jar進行編譯
java-library插件的一個特性是,消費該庫的項目在編譯時只需要classes文件夾,而不需要完整的JAR文件。這樣可以實現(xiàn)更輕量級的項目間依賴,因為只有在開發(fā)過程中執(zhí)行Java代碼編譯時才會執(zhí)行資源處理(processResources任務)和歸檔構建(jar任務)。
使用classes輸出而不是JAR是由消費者決定的。例如,Groovy消費者可能會請求classes和已處理的資源,因為這些可能在編譯過程中執(zhí)行AST轉(zhuǎn)換所需。
消費者的內(nèi)存使用增加
一個間接的后果是,增量檢查將需要更多的內(nèi)存,因為Gradle將對單個類文件進行快照,而不是單個jar文件。這可能會導致大型項目的內(nèi)存消耗增加,在某些情況下(例如,更改資源不再更改上游項目的compileJava任務的輸入),使得compileJava任務更容易處于最新狀態(tài)。
對于龐大的多項目,Windows系統(tǒng)會出現(xiàn)顯著的構建性能下降
對于快照處理的單個類文件,僅影響Windows系統(tǒng),當處理大量類文件時,性能可能顯著下降。這僅涉及非常大的多項目,其中通過使用許多api或(已棄用的)compile依賴項在類路徑上存在許多類。為了緩解這個問題,您可以將org.gradle.java.compile-classpath-packaging
系統(tǒng)屬性設置為true,以改變Java Library插件的行為,使用jar而不是class文件夾來處理編譯類路徑上的所有內(nèi)容。請注意,由于這會產(chǎn)生其他性能影響和潛在的副作用(通過觸發(fā)所有jar任務進行編譯),只建議在Windows上遇到上述性能問題時激活此選項。
發(fā)布庫
除了將庫發(fā)布到組件存儲庫外,有時您可能需要將庫及其依賴項打包到分發(fā)包中。Java Library Distribution插件就是為了幫助您完成這個任務。
參考鏈接
參考鏈接
小軍李:【Gradle jvm插件系列1】 Java Application插件權威詳解
小軍李:【Gradle jvm插件系列2】 Java Library插件用法示例權威詳解
小軍李:【Gradle jvm插件系列3】 Java platform平臺插件權威詳解
小軍李:【Gradle jvm插件系列4】 scala插件權威詳解
小軍李:【gradle多模塊系列1】多項目構建和子項目的添加管理
小軍李:【Gradle多模塊系列2】在子項目之間聲明依賴關系和共享構建邏輯示例詳解
小軍李:【Gradle 多模塊系列3】如何開發(fā)自定義Gradle插件
到此這篇關于Java Library插件權威詳解的文章就介紹到這了,更多相關Java Library插件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java并發(fā)編程之LongAdder執(zhí)行情況解析
這篇文章主要為大家介紹了Java并發(fā)編程之LongAdder執(zhí)行情況解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-04-04Java中new Date().getTime()指定時區(qū)的時間戳問題小結
本文主要介紹了Java中new Date().getTime()時間戳問題小結,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-07-07帶你輕松搞定Java面向?qū)ο蟮木幊?-數(shù)組,集合框架
Java是面向?qū)ο蟮母呒壘幊陶Z言,類和對象是 Java程序的構成核心。圍繞著Java類和Java對象,有三大基本特性:封裝是Java 類的編寫規(guī)范、繼承是類與類之間聯(lián)系的一種形式、而多態(tài)為系統(tǒng)組件或模塊之間解耦提供了解決方案2021-06-06guava中Multimap、HashMultimap用法小結
這篇文章主要介紹了guava中Multimap、HashMultimap使用,Multimap它可以很簡單的實現(xiàn)一些功能,LinkedHashMultimap實現(xiàn)類與HashMultimap類的實現(xiàn)方法一樣,唯一的區(qū)別是LinkedHashMultimap保存了記錄的插入順序,本文就這些內(nèi)容講解的非常詳細,需要的朋友參考下吧2022-05-05