构建工具之Gradle

Gradle 是一个基于Apache Ant 和 Apache Maven 概念的项目自动化建构工具。它使用一种基于 Groovy 的特定领域语言来声明项目设置,而不是传统的 XML。

之前其实写过一点 Gradle 的,那是因为写 Android 的时候在 AS 中使用的就是它来构建的,但是并没怎么深入,这次就来好好的看一下,当然深度感觉还是不够,下面的一些介绍直接 copy 过来的,也可以去看看当时的原文

Groovy是一种动态语言。这种语言比较有特点,它和Java一样,也运行于Java虚拟机中。恩??对头,简单粗暴点儿看,你可以认为Groovy扩展了Java语言。
比如,Groovy对自己的定义就是:Groovy是在 java平台上的、 具有像Python, Ruby 和 Smalltalk 语言特性的灵活动态语言, Groovy保证了这些特性像 Java语法一样被 Java开发者使用。

实际上,由于Groovy Code在真正执行的时候已经变成了Java字节码,所以JVM根本不知道自己运行的是Groovy代码
所以说,对于做Java的来说,可以无缝切换,它是一个构建工具,不是依赖管理工具,如果你的项目不需要复杂的构建流程,请不要尝试 Gradle,否则踩的坑根本补不过来,这个去网上看看就知道了,个人并不太喜欢它,也没用特别出众的地方。

安装

和之前的 Maven 差不多,都是下载、添加环境变量、命令行进行测试(gradle -v),环境变量一般命名 GRADLE_HOME 然后在 PATH 里添加 %GRADLE_HOME%\bin ;当然这是 win 的方法,其他系统等用到了再补充吧

每次执行 Gradle 的时候都会把其 init.d 目录下的脚本文件都执行一遍

官网:https://gradle.org/

另外,Gradle 的版本升级很恶心,各个版本还不兼容,所以每个项目需要特定版本的 Gradle,所以不建议自己下,让 IDEA 通过 wrapper 文件自动下载合适的版本是更好的选择;
一般会在用户目录下创建 .gradle 文件,里面缓存了各个版本,记得清理不需要的版本,否则。。。

Groovy的补充

Groovy 这门语言展开说还是挺多的,也许以后会详细了解,目前只需要知道几点它的特性就行了,它应该也算是“动态语言了”

  • 完全兼容 Java 语法
  • 分号是可选的
  • 类、方法默认是 public
  • Grooy 编译器会自动给属性添加 getter/setter 方法
  • 属性可以直接使用点号进行获取
  • 最后一个表达式的值会被当作返回值
  • == 相当于 equals(),所以不会产生空指针异常
  • 类型可选、括号可选、 assert 语句、字符串的定义(单引号、双引号、三个单引号)、闭包、集合API
    比如可以直接使用 def 来定义变量:def ver = 1println(ver)println ver 是一样的
    和其他语言类似,字符串的定义上,双引号中可以使用 ${} 引用变量,三个单引号可以换行

关于集合的简单使用:

1
2
3
4
5
6
7
8
9
10
11
// list
def listTest = ['abc','aaa']
// List追加数据
listTest << 'bbb'

// map
def mapTest = ['aaa':12,'bbb':13]
// map追加数据
mapTest.ccc = 456
println mapTest.aaa
println mapTest['bbb']

关于闭包的简单使用,闭包简单说就是个代码块,在其他语言中比如 python 中也是了解过的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 定义两个“闭包”,一般闭包用于复制给变量
// 这是一个带参数的闭包,v 就是参数
def c1 = {
v ->
println v
}
// 不带参数的闭包
def c2 = {
println 'hello'
}

// closure 就是接收闭包函数的,注意不要导入任何 java 的包
def method1(Closure closure){
closure('param')
}
def method2(Closure closure){
closure()
}

// 调用测试
method1(c1)
method2(c2)

初探Gradle

首先 Gradle 有三要素,GroupId、ArtifactId、Version
GroupId :一般就是公司域名的倒写,类似包名
ArtifactId :“编号”,要保证在组下面编号是唯一的(当然可以使用英文)

目录结构

目录结构类似:

|-src
|— main
|— |— java
|— |— resources
|— |— webapp
|— test
|— |— java
|— |— resources

构建一个项目

我用 IDEA 新建了个 Gradle 项目,自动给我生成的有 build.gradle 和 settings.gradle 这两个文件,第一个就是主要的配置文件了,第二个文件主要是加载模块用的,第一个文件最终会生成一个 Project 对象,之前定义的那三要素就相当于是里面的变量了,需要说下的是,如果有多个模块就要在 settings 文件中使用 include "module1", "module2", "module3" 进行导入了,下面会详细说

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
group 'com.bfchengnuo.gradletest'
version '1.0-SNAPSHOT'

apply plugin: 'java'
// apply plugin: 'war'

sourceCompatibility = 1.8

// 定义公共仓库
repositories {
maven {url 'http://maven.aliyun.com/nexus/content/groups/public/'}
mavenLocal()
mavenCentral()
}

// 定义依赖关系
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
}

配置 group、version 等属性除了在 build.gradle 文件中,还可以新建一个 gradle.properties 文件,然后这么写,效果是一样的

1
2
group='com.bfchengnuo.gradletest'
version='1.0-SNAPSHOT'

关于仓库配置:

  • mavenLocal():
    直接使用 ~/.m2/ 作为 maven 仓库的路径
  • mavenCentral():
    使用 maven 中央仓库 http://central.maven.org/ 作为 maven 仓库的路径
  • jcenter():
    使用 jcenter 仓库 http://jcenter.bintray.com/ 作为 maven 仓库路径,在国内通常比 mavenCentral() 快很多,有 CDN。
  • maven { url: '/path/to/custom/url' }:
    自定义的 maven 仓库路径

可以同时配置多个仓库,会按顺序查找。

关于构建脚本

Gradle 里的任何东西都是基于这两个基础概念:

  • projects ( 项目 )
  • tasks ( 任务 )

每一个构建都是由一个或多个 projects 构成的. 一个 project 到底代表什么取决于你想用 Gradle 做什么. 举个例子, 一个 project 可以代表一个 JAR 或者一个网页应用. 它也可能代表一个发布的 ZIP 压缩包, 这个 ZIP 可能是由许多其他项目的 JARs 构成的. 但是一个 project 不一定非要代表被构建的某个东西. 它可以代表一件XX要做的事, 比如部署你的应用.

每一个 project 是由一个或多个 tasks 构成的. 一个 task 代表一些更加细化的构建. 可能是编译一些 classes, 创建一个 JAR, 生成 javadoc, 或者生成某个目录的压缩文件. 我们经常使用的 tasks 多是由插件进行提供的

构建的生命周期

总体可分为:初始化 —-> 配置 —-> 执行

初始化阶段:就是看看配置了那些项目,把它们加进来
配置阶段:可以说只要不是特别的都属于配置,比如 apply、repositories、task 中的语句(不包括里面的doFirst、doLast ),这些语句都是,主要是用于处理各 task 的依赖关系
执行阶段:最简单的栗子就是 task 里的 doFirst、doLast 里面的代码,它们都属于执行阶段运行的

PS:每个阶段之后都会执行一个构造方法,平时一般用不到,就不说了

自定义Task

首先定义一个简单的任务试试:

1
2
3
4
5
6
7
8
9
10
// 第一种方式,task 是小写
task hello {
doLast { //实现doLast方法
println 'Hello world!'
}
}
// 第二种方式
task hello << {
println 'Hello world!'
}

<< 就等价于 doLastdoLast 是 gradle 提供访问 task 任务的一个 API,类似的还有 doFirst,当一个 task 被执行的时候,可以通过 doFirstdoLast 向 task 中动态添加操作。doFirstdoLast 会在 task 本身被执行之后才会被执行

如果使用依赖关系话,定义顺序其实是无所谓的…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
task hello << {
println 'Hello world!'
}
task intro(dependsOn: hello) << {
println "I'm Gradle"
}
// 或者你这么写
task intro(dependsOn: hello){
println 'hello' // 如果你写了这句,那么无论是否有依赖,这句永远第一执行
doLast {
println "I'm Gradle"
}
}
// 依赖还可以这样写
task intro() << {
dependsOn 'hello'
println "I'm Gradle"
}

依赖管理

对应最新的构建工具,怎么可能没有依赖管理的功能,并且它是兼容 Maven 的(依赖管理主要还是依赖 Maven,它的定位是构建工具,不是 Maven 这种依赖管理工具),也就是说,可以使用 Maven 仓库里的各种 jar 包依赖同样也分为编译时依赖和运行时依赖等,以前写 Maven 时候也有写过,差不多,然后再给一个 飞机链接
下面说下 Gradle 的依赖范围,在 Gradle 里,对依赖的定义有6种,他们分别是 compile, runtime, testCompile, testRuntime, providedCompile和 providedRuntime ;首先是 java 插件的依赖标准下,就是说一下常用的几个:

  • compile
    在所有的 classpath 中可用,同时它们也会被打包。大部分都是这一类
  • runtime
    在运行和测试系统的时候需要,但在编译的时候不需要。比如 JDBC 驱动实现,默认地依赖包括 compile 时的依赖
  • testCompile
    依赖在编译测试代码时需要,默认地依赖包括产品 class、已经 compile 时的依赖
  • testRuntime
    依赖在运行测试时需要,默认地依赖包括 compile、runtime 以及 testCompile 时的依赖

可以看到 test 依赖和程序代码依赖是分开的,也就是说 test 设置的依赖在程序代码里并不依赖它 ,然后下面再来说说 war 插件使用的依赖范围

  • providedCompile
    与 compile 作用类似,但不会被添加到最终的 war 包中 ,这是由于编译、测试阶段代码需要依赖此类 jar 包,而运行阶段容器已经提供了相应的支持,所以无需将这些文件打入到 war 包中了,例如 Servlet API 就是一个很明显的例子
  • providedRuntime
    同 providedCompile 类似,它表示一些在编译的时候不需要提供的依赖库,但是在运行的时候需要。它们一般也是已经包含到应用服务器中了。

相关的配置主要就是在下面的两个闭包中,定义仓库的瞬间就是查找的顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 定义公共仓库
repositories {
// 如果是自己的私服
maven{
url 'xxxx'
}
mavenLocal()
mavenCentral()
}

// 定义依赖关系
dependencies {
// 可以使用 : 进行分割,也可以使用全名
compile 'com.hynnet:logback-classic:1.1.3'
testCompile group: 'junit', name: 'junit', version: '4.12'
}

处理冲突

既然有传递依赖,那么肯定就有依赖冲突问题,Gradle 默认的处理方式是选择版本最高的,关于依赖的处理日志可以看 tasks 下的 help 下的 dependencies,当然是对于 IDEA 来说的

Gradle 提供了两种解决版本冲突的策略:Newest 和 Fail. 默认策略是 Newest,如果配置 Fail 模式,就需要加入下面的代码,这样 Gradle 就不会自动的处理冲突了,你也可以强制指定使用某一版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apply plugin: 'java'
configurations.all {
resolutionStrategy.failOnVersionConflict()
// 如果有冲突,强制依赖asm-all的3.31版本和commons-io的1.4版本
// resolutionStrategy.force 'asm:asm-all:3.3.1'
}

// 第二种写法,取消自动处理冲突
configurations.all {
resolutionStrategy{
failOnVersionConflict()
}
}
// 强制指定某个版本
configurations.all {
resolutionStrategy{
failOnVersionConflict()
force 'asm:asm-all:3.3.1'
}
}

或者使用排除传递中的依赖的方式,一般只使用一个 exclude 进行排除就行了

1
2
3
4
5
6
7
8
9
10
11
12
13
apply plugin: 'java'
dependencies {
compile('org.hibernate:hibernate:3.1') {
//如果有冲突,强制使用3.1版本
force = true
//排除传递中的依赖
exclude module: 'cglib' //通过 artifact 的名字排除
exclude group: 'org.jmock' //通过 artifact 的 group 名字排除
exclude group: 'org.unwanted', module: 'iAmBuggy' //通过artifact的名字和grop名字排除
//禁止该依赖传递
transitive = false
}
}

多项目构建

或者说多模块,新建模块的方法就不多说了,每个模块都含有 src、build.gradle ….等文件或目录,并且有些情况下各个模块之间是互相引用的,建好后如果需要引用其他模块,在 build.gradle 文件中这样引用

1
2
3
4
dependencies {
// 这里是有传递依赖的
compile project(':modeName')
}

别忘了最外层的 settings.gradle 文件中引入模块:include "module1", "module2", "module3"
通常,多个模块之间都有一些通用的配置,比如插件、版本等,这些都可以在根目录下的 build.gradle 文件中进行定义

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
// subprojects 里面可以写的内容和正常的build.gradle文件写法一样
// 为所有子模块应用设置
subprojects {
apply plugin: 'java'
apply plugin: 'eclipse-wtp'

repositories {
mavenCentral()
}

dependencies {
testCompile 'junit:junit:4.11'
}

version = '1.0'

jar {
manifest.attributes provider: 'gradle'
}
}

// 写 allprojects 貌似也可以
// 比如:所有模块都采用统一的版本号以及 groupName
allprojects {
group = 'com.bfchengnuo.test'
version = "0.1.0"
}

其中几个闭包的作用是:

方法名描述
allprojects配置当前模块以及所有子模块行为
subprojects配置所有子模块行为
project配置指定子模块行为

其他

常用的还有自动测试,稍微提一下是如果写了相关的测试代码,在执行 build 任务的时候是先处理源码,然后处理测试代码,如果测试代码通过则再进行打包或者XXX,如果测试不通过会直接报错,测试相关的日志可以去里看,有 html 格式的,看起来还不错,根据使用的测试框架(JUnit/ TestNG)也会有些差别

类被认为是一个JUnit测试类:

  • 类或父类集成自TestCase或GroovyTestCase
  • 类或父类有@RunWith注解
  • 类或者父类中的方法有@Test注解

再来说说发布,也许会用到要发布到私服或者本地仓库,那么可以使用 maven-publish 这个插件,当然必须是 Maven 仓库

下面是通过 ”maven-publish” 插件来支持发布到 Maven 功能。最终这种新的发布方式会替换掉通过 Upload task 的发布方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apply plugin: 'maven-publish'
publishing {
publications {
// mavenJava 可以自定义,可以设置多个,会同步到到 tasks 中去
mavenJava(MavenPublication) {
// 若是war包,就写 components.web
from components.java
}
}
}
publishing {
repositories {
maven {
name 'test' // 好像可以省略
// change to point to your repo, e.g. http://my.org/repo
url "$buildDir/repo"
}
}
}

然后找到相关的任务执行就可以了,上面的两个可以写在一个 publishing 中;关于 maven-publish 这个插件就不多说了,想进一步了解的可以去:http://blog.csdn.net/a38017032/article/details/65935573

参考/更多

https://dongchuan.gitbooks.io/gradle-user-guide-/build_script_basics/projects_and_tasks.html
http://www.zhaiqianfeng.com/2017/03/love-of-gradle-dependencies-1.html
https://michaelliuyang.github.io/projectsupport/2014/11/04/gradle-multi-project.html
一个多项目管理实例
Gradle User Guide 中文版
Gradle 实战–单元测试

喜欢就请我吃包辣条吧!

评论框加载失败,无法访问 Disqus

你可能需要魔法上网~~