Gradle入门(4):依赖管理
在现实生活中,要创造一个没有任何外部依赖的应用程序并非不可能,但也是极具挑战的。这也是为什么依赖管理对于每个软件项目都是至关重要的一部分。
这篇教程主要讲述如何使用Gradle管理我们项目的依赖,我们会学习配置应用仓库以及所需的依赖,我们也会理论联系实际,实现一个简单的演示程序。
Gradle支持以下仓库格式:
- Ivy仓库
- Maven仓库
- Flat directory仓库
在构建中加入Maven仓库
我们可以通过URL地址或本地文件系统地址,将Maven仓库加入到我们的构建中。
如果想通过URL地址添加一个Maven仓库,我们可以将以下代码片段加入到build.gradle文件中:
repositories { maven { url "http://maven.petrikainulainen.net/repo" } }
如果想通过本地文件系统地址添加一个Maven仓库,我们可以将以下代码片段加入到build.gradle文件中:
repositories { maven { url “../maven-repo” } }
在加入Maven仓库时,Gradle提供了三种“别名”供我们使用,它们分别是:
-
- mavenCentral()别名,表示依赖是从Central Maven 2 仓库中获取的。
- jcenter()别名,表示依赖是从Bintary’s JCenter Maven 仓库中获取的。
- mavenLocal()别名,表示依赖是从本地的Maven仓库中获取的。
如果我们想要将Central Maven 2 仓库加入到构建中,我们必须在build.gradle文件中加入以下代码片段:
repositories { mavenCentral() }
依赖管理简介
在配置完项目仓库后,我们可以声明其中的依赖,如果我们想要声明一个新的依赖,可以采用如下步骤:
- 指定依赖的配置。
- 声明所需的依赖。
让我们看一下详细步骤:
配置中的依赖分类
在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中,进行依赖声明:
dependencies { compile group: 'foo', name: 'foo', version: '0.1' }
我们也可以采用一种快捷方式声明依赖:[group]:[name]:[version]。如果我们想用这种方式,我们可以将以下代码段加入到build.gradle中:
dependencies { compile 'foo:foo:0.1' }
我们也可以在同一个配置项中加入多个依赖,传统的方式如下:
dependencies { compile ( [group: 'foo', name: 'foo', version: '0.1'], [group: 'bar', name: 'bar', version: '0.1'] ) }
如果采用快捷方式,那可以是这样:
dependencies { compile 'foo:foo:0.1', 'bar:bar:0.1' }
自然地,声明属于不同配置项的依赖也是可以的。比如说,如果我们想要声明属于compile和testCompile配置项的依赖,可以这么做:
dependencies { compile group: 'foo', name: 'foo‘, version: '0.1' testCompile group: 'test', name: 'test', version: '0.1' }
同样的,给力的快捷方式又来了:
dependencies { compile 'foo:foo:0.1' testCompile 'test:test:0.1' }
创建演示程序
演示程序的需求是这样的:
-
- 演示程序的构建脚本必须使用Maven central仓库。
- 演示程序必须使用Log4j写入日志。
- 演示程序必须包含包含单元测试,保证正确的信息返回,单元测试必须使用JUnit编写。
- 演示程序必须创建一个可执行的Jar文件。
我们来看一下怎样实现这些需求。
配置仓库
我们的演示程序的一个需求是构建脚本必须使用Maven central仓库,在我们使用Maven central仓库配置构建脚本后,源代码如下:
apply plugin: 'java' repositories { mavenCentral() } jar { manifest { attributes 'Main-Class': 'com.wuchao.gradle.HelloWorld' } }
我们再来看一下如何对我们的演示程序进行依赖声明。
依赖声明
在build.gradle文件中,我们声明了两个依赖:
-
- Log4j(版本1.2.17)用来记录日志。
- JUnit(版本4.11)用来编写单元测试。
在我们声明了这些依赖后,build.gradle文件是这样的:
apply plugin: 'java' repositories { mavenCentral() } dependencies { compile 'log4j:log4j:1.2.17' testCompile 'junit:junit:4.11' } jar { manifest { attributes 'Main-Class': 'com.wuchao.gradle.HelloWorld' } }
我们继续,稍微加入一些代码。
编写代码
为了实现我们演示程序的需求,“我们不得不过度工程化一下”,我们会按照下列步骤创建程序:
- 创建一个MessageService类,当其中的getMessage()方法被调用时,返回字符串“Hello World!”。
- 创建一个MessageServiceTest类,确保MessageService类中的getMessage()方法返回字符串“Hello World!”。
- 创建程序的主类,从MessageService对象获取信息,并使用Log4j写入日志。
- 配置Log4j。
我们按部就班的操作一下。
首先,在src/main/java/com/wuchao/gradle目录下新建一个MessageService类并加以实现,代码如下:
package com.wuchao.gradle; public class MessageService { public String getMessage() { return "Hello World!"; } }
其次,在src/test/java/com/wuchao/gradle目录下新建一个MessageServiceTest类,编写单元测试,代码如下:
package com.wuchao.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/com/wuchao/gradle目录下新建一个HelloWorld类,这是程序的主类,从MessageService对象获取信息,并使用Log4j写入日志,代码如下:
package com.wuchao.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文件如下:
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
这样就好了,我们看看如何执行测试。
gradle test
当测试通过时,我们能看到如下输出:
:compileJava :processResources :classes :compileTestJava Download https://jcenter.bintray.com/junit/junit/4.11/junit-4.11.pom Download https://jcenter.bintray.com/junit/junit/4.11/junit-4.11.jar :processTestResources UP-TO-DATE :testClasses :testPicked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=gasp BUILD SUCCESSFUL Total time: 5.9 secs
当我们执行单元测试时,Gradle会在相应目录创建测试报告:
-
- build/test-results目录包含每次测试执行的原始数据。
- build/reports/tests目录包含一个HTML报告,描述了测试的结果。
HTML测试报告是一个非常有用的工具,因为它描述了测试失败的原因。比如说,如果我们的单元测试认为MessageService类中的getMessage()方法返回字符串“Hello World0!”,那么HTML报告看上去就像下图一样:
我们继续,下面是如何打包和运行我们的演示程序。
打包和运行程序
我们能够可以使用以下任意一种命令打包程序:gradle assembly或gradle build,这两个命令都会在build/libs目录中创建dependency-management.jar文件。
当我们使用java -jar dependency-management.jar命令运行演示程序时,我们可以看到如下输出:
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/log4j/Logger at com.wuchao.gradle.HelloWorld.<clinit>(HelloWorld.java:6) Caused by: java.lang.ClassNotFoundException: org.apache.log4j.Logger at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ... 1 more
抛出异常的原因是,当我们运行程序时,Log4j的依赖在classpath中没有找到。
解决这个问题最简单的方式是创建一个所谓的“胖”Jar文件,即把所有程序运行所需的依赖都打包到Jar文件中去。
通过查阅Gradle Cookbook中的教程,可以修改构建脚本,如下:
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' } }
现在,我们可以运行演示程序了(打包后),一切正常:
wuchao@wuchao-PC:~/workspace/GradleWorkSpace/dependency-management/build/libs$ java -jar dependency-management.jar
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=gasp
INFO - HelloWorld - Received message: Hello World!