[TOC]

前言

最近在学习如何使用gradle,原本都是使用maven的,但随着gradle的广泛使用,开源项目基本上都改为gradle了。之前就碰到,构建Elasticsearch源码的时候,在gradle中找不到版本号定义的尴尬,所以尝试学习一下gradle

这个会是一个系列,主要记录我使用和学习时碰到的问题、方法及开源项目是如何使用gradle的。比较偏实战,希望自己可以保持学习、保持更新。

初步使用

Maven构建gradle项目

当前我的目标主要是将一个内部项目改为使用gradle,对于maven项目,gradle可以非常方便的直接进行转换,虽然后面还是需要少量的修改,但已经可以快速进行转换了。

我们先看一下在maven下的目录:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
├── HELP.md
├── honlyc-sample.iml
├── mvnw
├── mvnw.cmd
├── pom.xml
└── server
    ├── HELP.md
    ├── pom.xml
    ├── server.iml
    └── src

这是直接使用Spring-Initler初始化的一个项目,包含了一个子模块server。接下来我们会在这个项目进行我们的gradle学习和使用,首先,让我们进行转换。

maven项目的根路径,直接执行命令:

1
2
3
4
5
6
7
8
9
gradle init
// 执行后会提示你是否从 pom 构建:
Found a Maven build. Generate a Gradle build from this? (default: yes) [yes, no]
// 输入 yes ,会让选择使用 grovvy 还是 kotlin,我选的是 kotlin。
Select build script DSL:
  1: Groovy
  2: Kotlin
Enter selection (default: Groovy) [1..2] 2
// 这里选 kotlin,主要后面有地方需要用到 kotlin 相关的特性

执行完成后,就可以看到gradle相关文件就已经创建好了。在看下目录结构:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
├── HELP.md
├── buildSrc
│   ├── build.gradle.kts
│   └── src
├── gradle
│   └── wrapper
├── gradlew
├── gradlew.bat
├── honlyc-sample.iml
├── mvnw
├── mvnw.cmd
├── pom.xml
├── server
│   ├── HELP.md
│   ├── build.gradle.kts
│   ├── pom.xml
│   ├── server.iml
│   └── src
└── settings.gradle.kts

可以看到,一键转换还是非常好用的,而且,我当前gradle版本是6.7.1,在构建时,自动给你使用了buildSrc的方式,我们待会再详细讲解这个。

这时,我们在IDEA里面打开任意build.gradle.kts文件,就会提示你Link Gradle Project,点击,即可在IDEA里面使用gradle编译了。

image-20210421150046084

可以看到,在使用gradle编译后,buildSrc是自动编译成一个模块了。

好了,从maven转换成gradle,就暂时搞定,接下来就是各种实际的使用了。

buildSrc分析

在看buildSrc之前,我们先看一下server模块的build.gradle.kts文件:

1
2
3
4
5
6
7
8
9
/*
 * This file was generated by the Gradle 'init' task.
 */

plugins {
    id("com.honlyc.java-conventions")
}

description = "server"

咦,这里面只有一个pluginsdescription,那我的依赖呢?版本呢?带着这个疑问,我们再来看buildSrc模块,buildSrc模块也很简单,先看结构:

image-20210421152908102

就两个文件,我们分别看:

build.gradle.kts内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
/*
 * This file was generated by the Gradle 'init' task.
 */

plugins {
    // Support convention plugins written in Kotlin. Convention plugins are build scripts in 'src/main' that automatically become available as plugins in the main build.
    `kotlin-dsl`
}

repositories {
    // Use the plugin portal to apply community plugins in convention plugins.
    gradlePluginPortal()
}

这个文件也很简单,依赖一个plugin,定义了一个repositories,还是没有看到我们想要的依赖、版本这些的定义。接着看另外一个文件,com.honlyc.java-conventions.gradle.kts内容:

 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
28
29
30
31
32
33
34
35
36
37
38
39
/*
 * This file was generated by the Gradle 'init' task.
 */

plugins {
    `java-library`
    `maven-publish`
}

repositories {
    mavenLocal()
    maven {
        url = uri("https://repo.maven.apache.org/maven2/")
    }
}

dependencies {
    implementation("commons-dbutils:commons-dbutils:1.4")
    implementation("commons-lang:commons-lang:2.6")
    implementation("commons-collections:commons-collections:3.2.1")
    implementation("commons-io:commons-io:2.1")
    implementation("commons-logging:commons-logging:1.1.1")
    testImplementation("junit:junit:4.12")
}

group = "com.honlyc"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_1_8

publishing {
    publications.create<MavenPublication>("maven") {
        from(components["java"])
    }
}

tasks.withType<JavaCompile>() {
    options.encoding = "UTF-8"
}

终于,我们在这里看到了依赖、版本、仓库等内容,同时,我们也能够发现,这个我文件的命名是com.honlyc.java-conventions开头,这跟我们在server中依赖的插件名称一致。

其实,buildSrc里面,这种写法就是自定义了一个gradle插件,插件名称为com.honlyc.java-conventions,这个插件里面定义了整个项目的仓库、名称、版本、公共依赖等一系列项目内共有的属性。其他所有的子模块,都只需要依赖这个插件,就可以直接应用到所有的内容,同时,也可以在子模块内部有自己的依赖。

这样,在管理上,就一目了然了;在结构上,因为引入了buildSrc,所以也没有了跟settings.gradle.kts同级的build.gradle.kts文件的存在;

关于buildSrc,我这里只是简单介绍,它还有很多使用技巧,后续我会陆续进行记录的。

lombok使用

相信很多小伙伴在项目中都会用到lombok这个工具,而在gradle中,它有不同的引入方式。我们这个项目因为使用了buildSrc,在使用上,又会有所不同,接下来,让我们一起来看看怎么用起来吧。

首先,我们要在buildSrc/build.gradle.kts文件中添加一个依赖:

1
2
3
dependencies {
    implementation("io.freefair.gradle:lombok-plugin:6.0.0-m2")
}

表示buildSrc这个项目依赖了lombok,但这个时候,这个依赖并不会传递到server等子模块上去,我们需要在插件里面去引用才行。

编辑buildSrc/../com.honlyc.java-conventions.gradle.kts文件,添加一个插件依赖:

1
2
3
4
5
plugins {
    `java-library`
    `maven-publish`
    id("io.freefair.lombok") // 添加的 lombok 插件,这里不需要写版本号,因为在外面已经依赖了
}

gradle build一下,我们就可以在server模块直接使用lombok啦:

1
2
3
4
@Data
@Slf4j
public class BuildRes {
}

不需要改动子模块,只需要在自定义插件里面进行修改即可,可以说是超级方便了。

依赖库自动补全

在使用gradle时,有一点很难受,在依赖库时,没有代码补全,往往每次依赖都要去找,而在maven中,是有代码补全的。

这里,我们可以使用kotlin DSL的特性,结合buildSrc来实现依赖库的伪代码补全,为什么说是呢?因为这个方式不是全自动的。

废话不多说,直接上代码:

首先,在buildSrc项目下,创建一个Dependencies.kt文件:

image-20210421155729056

然后,把我们需要依赖的库、版本,分别定义到这个类中:

 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
object Deps {
    val jmh = Jmh
    val log = Log
    val lib = Lib

    object Jmh {
        const val plugin = "me.champeau.gradle:jmh-gradle-plugin:0.5.0-rc-2"
        const val core = "org.openjdk.jmh:jmh-core:1.23"
    }

    object Log {
        const val commonLog = "commons-logging:commons-logging:1.2"
    }

    object Lib {
        const val json2017 = "org.json:json:20170516"
        const val luceneCore = "org.apache.lucene:lucene-core:7.1.0"
        const val luceneAnalyzer = "org.apache.lucene:lucene-analyzers-common:7.1.0"
        const val mapdb = "org.mapdb:mapdb:3.0.8"
        
        const val log4jApi = "org.apache.logging.log4j:log4j-api:2.11.2"
        const val log4jSlf4j = "org.apache.logging.log4j:log4j-slf4j-impl:2.11.2"
    }

}

gradle编译后,就可以直接使用啦,在需要依赖时,直接使用:

1
2
implementation(Deps.lib.log4jApi)
implementation(Deps.lib.log4jSlf4j)

这个时候,因为相当于使用了kotlin代码,所以,这里是会有代码补全提示的,就可以很愉快地使用啦。

当然,到这里,小伙伴们应该就发现问题了,这也是我为什么说是伪代码补全的原因,因为这个补全的前提是,我们需要在Deps类中定义好我们所需要的依赖,这个定义的过程,依旧还是需要我们去找、去查。

尽管如此,还是友好太多了,我们只需要找一次,编写一次,然后在项目中,就可以随意使用、还是带代码补全的,效率提高可不止一点点哈。

模块嵌套

在日常使用中,我们可能会存在模块嵌套,就是多个子模块有共同的父模块,并且有相同的依赖,这时就需要用到模块嵌套了。在gradle中,模块的嵌套语法其实很简单.

先看我们的项目结构:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
...
├── provider
│   ├── build.gradle.kts
│   ├── provider-one
│   └── provider-two
├── server
│   ├── HELP.md
│   ├── build
│   ├── build.gradle.kts
│   ├── pom.xml
│   └── src
└── settings.gradle.kts

这里我们有一个provider的模块,下面有有两个子模块provider-oneprovider-two,他们两个是两个不同的实现,但是有一些相同的依赖。这时我们可以在provider下定义一个子模块依赖。

编写provider/build.gradle.kts文件内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 这里表示当前模块依赖的插件
plugins {
    id("com.honlyc.java-conventions")
}

subprojects {
    // 这里表示所有子模块所依赖的插件
    apply(plugin = "com.honlyc.java-conventions")

    // 所有子模块所依赖的库
    dependencies {
        implementation(Deps.jmh.core)
    }
}

// 当前模块名称
description = "provider"

这样定义后,该模块下的子模块就很简单了。

编写provider/provider-one/build.gradle.kts文件内容:

1
2
3
4
5
dependencies {
	// 内部其他的依赖
}

description = "provider-one"

编写provider/provider-two/build.gradle.kts文件内容:

1
2
3
4
5
dependencies {
	// 内部其他的依赖
}

description = "provider-two"

我们可以发现,这两个子模块并没有再定义plugins了,因为在夫模块中已经定义好了。这样是不是更为简洁,同时把公共依赖也提取到夫模块中去了。Nice~~

总结

整个转换过程当中,还是遇到很多问题,搞了差不多一天,才完整地弄完,并能够运行。感觉上,gradle还是比maven要方便很多,同样,需要学的也变多了。

我主要是苦mavenversion升级久矣,每次升级打包,就得心跳一番。在gradle完全就没这个问题,只需要改一个地方,直接打包就好了。

因为也是初学,有说的不对的地方,小伙伴们可以积极指出,我会及时改正。

以上。

参考

https://juejin.cn/post/6844903615346245646

https://medium.com/bumble-tech/how-to-use-composite-builds-as-a-replacement-of-buildsrc-in-gradle-64ff99344b58

https://www.jianshu.com/p/8962d6ba936e

https://blog.csdn.net/weixin_34092455/article/details/89045881