在現實生活中,要創造一個沒有任何外部依賴的應用程式並非不可能,但也是極具挑戰的。這也是為什麼依賴管理對於每個軟體專案都是至關重要的一部分。
這篇教程主要講述如何使用Gradle管理我們專案的依賴,我們會學習配置應用倉庫以及所需的依賴,我們也會理論聯絡實際,實現一個簡單的演示程式。
讓我們開始吧。
倉庫管理簡介
本質上說,倉庫是一種存放依賴的容器,每一個專案都具備一個或多個倉庫。
Gradle支援以下倉庫格式:
- Ivy倉庫
- Maven倉庫
- Flat directory倉庫
我們來看一下,對於每一種倉庫型別,我們在構建中應該如何配置。
在構建中加入Ivy倉庫
我們可以通過URL地址或本地檔案系統地址,將Ivy倉庫加入到我們的構建中。
如果想通過URL地址新增一個Ivy倉庫,我們可以將以下程式碼片段加入到build.gradle檔案中:
1 2 3 4 5 |
repositories { ivy { url "http://ivy.petrikainulainen.net/repo" } } |
如果想通過本地檔案系統地址新增一個Ivy倉庫,我們可以將以下程式碼片段加入到build.gradle檔案中:
1 2 3 4 5 |
repositories { ivy { url "../ivy-repo" } } |
小貼士:如果你想要獲得更多關於Ivy倉庫配置的資訊,你可以參考以下資源:
- Section 50.6.6 Ivy Repositories of the Gradle User Guide
- The API documentation of the IvyArtifactRepository
我們繼續,下面是如何在構建中加入Maven倉庫。
在構建中加入Maven倉庫
與Ivy倉庫很類似,我們可以通過URL地址或本地檔案系統地址,將Maven倉庫加入到我們的構建中。
如果想通過URL地址新增一個Maven倉庫,我們可以將以下程式碼片段加入到build.gradle檔案中:
1 2 3 4 5 |
repositories { maven { url "http://maven.petrikainulainen.net/repo" } } |
如果想通過本地檔案系統地址新增一個Maven倉庫,我們可以將以下程式碼片段加入到build.gradle檔案中:
1 2 3 4 5 |
repositories { maven { url "../maven-repo" } } |
在加入Maven倉庫時,Gradle提供了三種“別名”供我們使用,它們分別是:
- mavenCentral()別名,表示依賴是從Central Maven 2 倉庫中獲取的。
- jcenter()別名,表示依賴是從Bintary’s JCenter Maven 倉庫中獲取的。
- mavenLocal()別名,表示依賴是從本地的Maven倉庫中獲取的。
如果我們想要將Central Maven 2 倉庫加入到構建中,我們必須在build.gradle檔案中加入以下程式碼片段:
1 2 3 |
repositories { mavenCentral() } |
小貼士:如果你想要獲取更多關於Maven倉庫配置的資訊,你可以參考這篇文章:
section 50.6.4 Maven Repositories of the Gradle User Guide
我們繼續,下面是如何在構建中加入Flat Directory倉庫。
在構建中加入Flat Directory倉庫
如果我們想要使用Flat Directory倉庫,我們需要將以下程式碼片段加入到build.gradle檔案中:
1 2 3 4 5 |
repositories { flatDir { dirs 'lib' } } |
這意味著系統將在lib目錄下搜尋依賴,同樣的,如果你願意的話可以加入多個目錄,程式碼片段如下:
1 2 3 4 5 |
repositories { flatDir { dirs 'libA', 'libB' } } |
小貼士:如果你想要獲得更多關於Flat Directory倉庫配置的資訊,你可以參考以下資源:
- Section 50.6.5 Flat directory repository of the Gradle User Guide
- Flat Dir Repository post to the gradle-user mailing list
我們繼續,下面要講的是,如何使用Gradle管理專案中的依賴。
依賴管理簡介
在配置完專案倉庫後,我們可以宣告其中的依賴,如果我們想要宣告一個新的依賴,可以採用如下步驟:
- 指定依賴的配置。
- 宣告所需的依賴。
讓我們看一下詳細步驟:
配置中的依賴分類
在Gradle中,依賴是按照指定名稱進行分類的,這些分類被稱為配置項,我們可以使用配置項宣告專案的外部依賴。
Java外掛指定了若干依賴配置項,其描述如下:
- 當專案的原始碼被編譯時,compile配置項中的依賴是必須的。
- runtime配置項中包含的依賴在執行時是必須的。
- testCompile配置項中包含的依賴在編譯專案的測試程式碼時是必須的。
- testRuntime配置項中包含的依賴在執行測試程式碼時是必須的。
- archives配置項中包含專案生成的檔案(如Jar檔案)。
- default配置項中包含執行時必須的依賴。
我們繼續,下面是如何在專案中宣告依賴。
宣告專案依賴
最普遍的依賴稱為外部依賴,這些依賴存放在外部倉庫中。一個外部依賴可以由以下屬性指定:
- group屬性指定依賴的分組(在Maven中,就是groupId)。
- name屬性指定依賴的名稱(在Maven中,就是artifactId)。
- version屬性指定外部依賴的版本(在Maven中,就是version)。
小貼士:這些屬性在Maven倉庫中是必須的,如果你使用其他倉庫,一些屬性可能是可選的。打個比方,如果你使用Flat directory倉庫,你可能只需要指定名稱和版本。
我們假設我們需要指定以下依賴:
- 依賴的分組是foo。
- 依賴的名稱是foo。
- 依賴的版本是0.1。
- 在專案編譯時需要這些依賴。
我們可以將以下程式碼片段加入到build.gradle中,進行依賴宣告:
1 2 3 |
dependencies { compile group: 'foo', name: 'foo', version: '0.1' } |
我們也可以採用一種快捷方式宣告依賴:[group]:[name]:[version]。如果我們想用這種方式,我們可以將以下程式碼段加入到build.gradle中:
1 2 3 |
dependencies { compile 'foo:foo:0.1' } |
我們也可以在同一個配置項中加入多個依賴,傳統的方式如下:
1 2 3 4 5 6 |
dependencies { compile ( [group: 'foo', name: 'foo', version: '0.1'], [group: 'bar', name: 'bar', version: '0.1'] ) } |
如果採用快捷方式,那可以是這樣:
1 2 3 |
dependencies { compile 'foo:foo:0.1', 'bar:bar:0.1' } |
自然地,宣告屬於不同配置項的依賴也是可以的。比如說,如果我們想要宣告屬於compile和testCompile配置項的依賴,可以這麼做:
1 2 3 4 |
dependencies { compile group: 'foo', name: 'foo', version: '0.1' testCompile group: 'test', name: 'test', version: '0.1' } |
同樣的,給力的快捷方式又來了( ̄︶ ̄)
1 2 3 4 |
dependencies { compile 'foo:foo:0.1' testCompile 'test:test:0.1' } |
小貼士:你可以從這篇文章中獲得更多關於依賴宣告的資訊。
我們已經學習了依賴管理的基礎知識,下面我們來實現一個演示程式。
建立演示程式
演示程式的需求是這樣的:
- 演示程式的構建指令碼必須使用Maven central倉庫。
- 演示程式必須使用Log4j寫入日誌。
- 演示程式必須包含包含單元測試,保證正確的資訊返回,單元測試必須使用JUnit編寫。
- 演示程式必須建立一個可執行的Jar檔案。
我們來看一下怎樣實現這些需求。
配置倉庫
我們的演示程式的一個需求是構建指令碼必須使用Maven central倉庫,在我們使用Maven central倉庫配置構建指令碼後,原始碼如下:
1 2 3 4 5 6 7 8 9 10 11 |
apply plugin: 'java' repositories { mavenCentral() } jar { manifest { attributes 'Main-Class': 'net.petrikainulainen.gradle.HelloWorld' } } |
我們再來看一下如何對我們的演示程式進行依賴宣告。
依賴宣告
在build.gradle檔案中,我們宣告瞭兩個依賴:
- Log4j(版本1.2.17)用來記錄日誌。
- JUnit(版本4.11)用來編寫單元測試。
在我們宣告瞭這些依賴後,build.gradle檔案是這樣的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
apply plugin: 'java' repositories { mavenCentral() } dependencies { compile 'log4j:log4j:1.2.17' testCompile 'junit:junit:4.11' } jar { manifest { attributes 'Main-Class': 'net.petrikainulainen.gradle.HelloWorld' } } |
我們繼續,稍微加入一些程式碼。
編寫程式碼
為了實現我們演示程式的需求,“我們不得不過度工程化一下”,我們會按照下列步驟建立程式:
- 建立一個MessageService類,當其中的getMessage()方法被呼叫時,返回字串“Hello World!”。
- 建立一個MessageServiceTest類,確保MessageService類中的getMessage()方法返回字串“Hello World!”。
- 建立程式的主類,從MessageService物件獲取資訊,並使用Log4j寫入日誌。
- 配置Log4j。
我們按部就班的操作一下。
首先,在src/main/java/net/petrikainulainen/gradle目錄下新建一個MessageService類並加以實現,程式碼如下:
1 2 3 4 5 6 7 8 |
package net.petrikainulainen.gradle; public class MessageService { public String getMessage() { return "Hello World!"; } } |
其次,在src/main/test/net/petrikainulainen/gradle目錄下新建一個MessageServiceTest類,編寫單元測試,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package net.petrikainulainen.gradle; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; public class MessageServiceTest { private MessageService messageService; @Before public void setUp() { messageService = new MessageService(); } @Test public void getMessage_ShouldReturnMessage() { assertEquals("Hello World!", messageService.getMessage()); } } |
第三,在src/main/java/net/petrikainulainen/gradle目錄下新建一個HelloWorld類,這是程式的主類,從MessageService物件獲取資訊,並使用Log4j寫入日誌,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package net.petrikainulainen.gradle; import org.apache.log4j.Logger; public class HelloWorld { private static final Logger LOGGER = Logger.getLogger(HelloWorld.class); public static void main(String[] args) { MessageService messageService = new MessageService(); String message = messageService.getMessage(); LOGGER.info("Received message: " + message); } } |
第四,在src/main/resources目錄中,使用log4j.properties配置log4j,log4j.properties檔案如下:
1 2 3 4 5 |
log4j.appender.Stdout=org.apache.log4j.ConsoleAppender log4j.appender.Stdout.layout=org.apache.log4j.PatternLayout log4j.appender.Stdout.layout.conversionPattern=%-5p - %-26.26c{1} - %m\n log4j.rootLogger=DEBUG,Stdout |
這樣就好了,我們看看如何執行測試。
執行測試
我們可以通過以下命令執行測試。
1 |
gradle test |
當測試通過時,我們能看到如下輸出:
1 2 3 4 5 6 7 8 9 10 11 12 |
> gradle test :compileJava :processResources :classes :compileTestJava :processTestResources :testClasses :test BUILD SUCCESSFUL Total time: 4.678 secs |
然而,如果測試失敗,我們將看到如下輸出:
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 |
> gradle test :compileJava :processResources :classes :compileTestJava :processTestResources :testClasses :test net.petrikainulainen.gradle.MessageServiceTest > getMessage_ShouldReturnMessageFAILED org.junit.ComparisonFailure at MessageServiceTest.java:22 1 test completed, 1 failed :test FAILED FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':test'. > There were failing tests. See the report at: file:///Users/loke/Projects/Java/Blog/gradle-examples/dependency-management/build/reports/tests/index.html * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. BUILD FAILED Total time: 4.461 secs |
正如我們所看到的,如果單元測試失敗了,輸出資訊中將描述以下資訊:
- 哪一個測試失敗了。
- 執行了幾個測試,其中幾個失敗了。
- 測試報告的位置,測試報告提供了失敗(或成功)的測試的額外資訊。
當我們執行單元測試時,Gradle會在相應目錄建立測試報告:
- build/test-results目錄包含每次測試執行的原始資料。
- build/reports/tests目錄包含一個HTML報告,描述了測試的結果。
HTML測試報告是一個非常有用的工具,因為它描述了測試失敗的原因。比如說,如果我們的單元測試認為MessageService類中的getMessage()方法返回字串“Hello Worl1d!”,那麼HTML報告看上去就像下圖一樣:
我們繼續,下面是如何打包和執行我們的演示程式。
打包和執行程式
我們能夠可以使用以下任意一種命令打包程式:gradle assembly或gradle build,這兩個命令都會在build/libs目錄中建立dependency-management.jar檔案。
當我們使用java -jar dependency-management.jar命令執行演示程式時,我們可以看到如下輸出:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
> java -jar dependency-management.jar Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/log4j/Logger at net.petrikainulainen.gradle.HelloWorld.<clinit>(HelloWorld.java:10) Caused by: java.lang.ClassNotFoundException: org.apache.log4j.Logger at java.net.URLClassLoader$1.run(URLClassLoader.java:372) at java.net.URLClassLoader$1.run(URLClassLoader.java:361) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:360) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ... 1 more |
丟擲異常的原因是,當我們執行程式時,Log4j的依賴在classpath中沒有找到。
解決這個問題最簡單的方式是建立一個所謂的“胖”Jar檔案,即把所有程式執行所需的依賴都打包到Jar檔案中去。
通過查閱Gradle Cookbook中的教程,可以修改構建指令碼,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
apply plugin: 'java' repositories { mavenCentral() } dependencies { compile 'log4j:log4j:1.2.17' testCompile 'junit:junit:4.11' } jar { from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } manifest { attributes 'Main-Class': 'net.petrikainulainen.gradle.HelloWorld' } } |
現在,我們可以執行演示程式了(打包後),一切正常:
1 2 |
> java -jar dependency-management.jar INFO - HelloWorld - Received message: Hello World! |
這些就是今天的內容了,我們總結一下學到了什麼。
總結
這篇教程教會了我們四個方面的內容:
- 我們學會了配置構建所需的倉庫。
- 我們學會了如何宣告所需的依賴以及依賴的分類(分組)。
- 我們瞭解了Gradle會在測試執行後建立一個HTML測試報告。
- 我們學會了建立一個所謂的“胖”Jar檔案。
如果你想要玩一玩這篇教程所涉及的演示程式,你可以從GitHub那獲取。