初始化Gradle项目

初始化

1
gradle init

选择当前项目的类型,选择2应用类型:

1
2
3
4
5
6
7
8
> Task :wrapper

Select type of project to generate:
  1: basic
  2: application
  3: library
  4: Gradle plugin
Enter selection (default: basic) [1..4] 2

选择语言类型,使用Java语言讲解,选择3号:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
> Task :init

Select implementation language:
  1: C++
  2: Groovy
  3: Java
  4: Kotlin
  5: Scala
  6: Swift
Enter selection (default: Java) [1..6] 3

是否生成多个子项目结构,单个项目选择no

1
Generate multiple subprojects for application? (default: no) [yes, no] no

接着是编写Gradle脚本采用的语言,选择1号:

1
2
3
4
Select build script DSL:
  1: Kotlin
  2: Groovy
Enter selection (default: Kotlin) [1..2] 1

测试使用的框架,默认:

1
2
3
4
5
6
Select test framework:
  1: JUnit 4
  2: TestNG
  3: Spock
  4: JUnit Jupiter
Enter selection (default: JUnit Jupiter) [1..4] 

接着是当前项目的名称以及包名,默认就是当前目录名字:

1
2
3
Project name (default: Test):

Source package (default: test): com.test

最后是采用的Java版本,是否选择使用新特性:

1
2
3
Enter target version of Java (min. 7) (default: 17): 17

Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no) [yes, no]
1
2
3
4
5
> Task :init
To learn more about Gradle by exploring our Samples at https://docs.gradle.org/8.5/samples/sample_building_java_applications.html

BUILD SUCCESSFUL in 12m 38s
2 actionable tasks: 2 executed

目录结构

  • .gradle:Gradle自动生成的项目缓存目录。
  • .idea:这个是IDEA的项目配置目录,跟Gradle生成的没关系,无视掉就行。
  • app:存放整个项目的源代码、测试等,这里面就是我们写代码的地方了。
    • build.gradle.kts:项目的gradle构建脚本。
    • src:存放源代码和测试代码。
      • main:编写所有项目核心代码。
      • test:编写项目测试代码。
  • gradle:包含JAR文件和Gradle Wrapper的配置。
  • gradlew:适用于macOS和Linux的使用Gradle Wrapper执行构建的脚本(这里的版本就是GradleWrapper指定的版本)
  • gradlew.bat:适用于Windows的使用Gradle Wrapper执行构建的脚本。
  • settings.gradle.kts:定义子项目列表的项目配置文件,也是最关键的设置文件。

Gradle项目在生成时默认创建了一个测试的主类:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
/*
 * This Java source file was generated by the Gradle 'init' task.
 */
package com.test;

public class App {
    public String getGreeting() {
        return "Hello World!";
    }

    public static void main(String[] args) {
        System.out.println(new App().getGreeting());
    }
}

Gradle常用命令

首先我们可以查看一下所有Gradle支持的任务,这里我们使用GradleWapper提供的gradlew进行操作

1
./gradlew.bat task
1
./gradlew task

清理构建文件

1
./gradlew clean

编译代码:

1
2
./gradlew classes
./gradlew classes -q  #安静模式,只执行,不打印日志

测试代码:

1
./gradlew test
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class AppTest {
    //默认使用的是JUnit作为单元测试工具
    @Test
    void appHasAGreeting() {
        App classUnderTest = new App();
        //这里是判断如果结果为Null的话就抛出异常
        assertNotNull(classUnderTest.getGreeting(), "app should have a greeting");
    }
}

public class App {
    public String getGreeting() {
        return null;
    }
}

测试失败,并且Gradle自动为我们生成了一个错误报告,其中明确指出了我们出现错误的测试用例:

完整的编译+测试流程来构建整个项目,并得到打包好的jar等文件:

1
./gradlew build

不执行测试,只想构建整个项目,也可以添加参数跳过:

1
./gradlew build -x test

项目配置

配置文件介绍

settings.gradle

是整个Gradle项目的入口点,用于定义所有的子项目,并让它们参与到构建中,Gradle支持单项目和多项目的构建:

  • 对于单个项目构建,设置文件是可选的。
  • 对于多项目构建,设置文件必须存在,并在其中定义所有子项目。

一个标准的Gradle设置文件按照以下样式进行编写,这里使用Kotlin语言介绍:

1
2
3
4
5
rootProject.name = "root-project"   //rootProject对象代表当前这个项目,其name属性就是当前项目的名称

include("sub-project-a")     //所有的子项目使用include()函数进行添加,如果没有子项目可以不写
include("sub-project-b")
include("sub-project-c")

build.gradle

对应的构建配置,这里我们使用的是Kotlin语言,因此项目中会存在一个build.gradle.kts文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
plugins {
    id("java")
}

group = "cn.itbaima"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()
}

dependencies {
    testImplementation(platform("org.junit:junit-bom:5.9.1"))
    testImplementation("org.junit.jupiter:junit-jupiter")
}

tasks.test {
    useJUnitPlatform()
}

plugins函数,后面的Lambda中编写了当前项目需要使用到的插件:

1
2
3
plugins {
    id("java")
}

使用id()函数指定需要使用的插件,这里使用的是java插件,java插件将Java编译以及测试和捆绑功能添加到项目中,为建任何类型的 Java 项目提供支持。

除了java插件之外,我们如果需要构建其他类型的项目,也可以使用多种多样的插件:

1
2
3
plugins {
    id("cpp-application")   //用于构建C++应用程序  
}
1
2
3
plugins {
    id("swift-application")   //用于在MacOS构建Swift应用程序
}
1
2
3
plugins {
    id("org.jetbrains.kotlin.jvm") version "1.9.0"  //使用version中缀函数为id指定的插件添加版本
}

group和version分别设置了当前项目所属的组名称和版本:

1
2
group = "cn.itbaima"
version = "1.0-SNAPSHOT"

所有依赖的仓库配置,默认使用的是Maven中心仓库:

1
2
3
repositories {
    mavenCentral()
}

依赖列表,这里导入JUnit相关依赖用于测试:

1
2
3
4
dependencies {
    testImplementation(platform("org.junit:junit-bom:5.9.1"))
    testImplementation("org.junit.jupiter:junit-jupiter")
}

任务相关配置,这里对test任务进行了相关配置:

1
2
3
tasks.test {
    useJUnitPlatform()
}

从下节课开始我们来详细介绍一下各个部分。

编写设置文件

设置文件settings.gradle.kts则是整个构建的入口点,在Gradle构建生命周期的早期,初始化阶段会在项目根目录中找到设置文件。

当找到设置文件settings.gradle(.kts)时,Gradle会实例化一个settings对象,我们可以通过此对象来声明要包含在构建中的所有项目,包括我们项目名称的声明也是通过它来完成:

1
settings.rootProject.name = "untitled"

我们也可以省略掉settings直接使用其提供的属性:

1
rootProject.name = "untitled"

常用属性:

姓名描述
buildCache项目构建所用缓存配置。
plugins用于设置的插件。
rootDir项目构建的根目录,根目录是整个项目目录最外层。
rootProject构建的根项目。
settings返回设置对象。

常用方法:

姓名描述
include()将指定名称的项目添加到构建列表中。
includeBuild()添加指定路径上的其他Gradle项目到构建列表中。

配置基础

编写配置文件时,本质上是对Gradle API的一系列方法调用,结合Groovy或Kotlin的{...} 语法(在Groovy中称作闭包,Kotlin中称为Lambda)能够轻松编写非常简洁的配置,比如配置插件:

1
2
3
4
5
rootProject.name = "untitled"

plugins {  //使用plugins函数配置插件,结合Lambda表达式可以使用简洁语法完成配置
    id("test")   //瞎写的一个
}

在配置文件中定义的语句,Gradle会一行一行地向下执行

实际上,我们编写的配置文件在执行Gradle构建时会编译为对应的class文件加载执行,这些文件存放在Gradle的缓存目录中:

直接在Gradle配置中编写的自定义语句也可以执行

可以执行了自定义的打印语句,包括可以通过rootProject对象拿到当前的项目文件File对象等。

在Gradle中,和Maven一样也分为插件和依赖,我们可以在settings.gradle.kt中可以为所有的项目进行统一配置,比如要修改获取插件的仓库位置:

1
2
3
4
5
6
pluginManagement {   //使用pluginManagement函数配置插件仓库列表
    repositories {   //在repositories函数中配置需要用的仓库
        gradlePluginPortal()   //Gradle插件仓库
        google()    //Google插件仓库
    }
}

修改为国内的阿里云镜像:

1
2
3
4
5
6
7
8
pluginManagement {
    repositories {
        //手动指定maven仓库地址,修改URL地址
        maven {
            setUrl("https://maven.aliyun.com/repository/public/")
        }
    }
}

同样的,对于所有的依赖,也可以直接配置为国内的阿里云镜像仓库:

1
2
3
4
5
6
7
dependencyResolutionManagement {   //依赖解析管理中可以配置全局依赖仓库
    repositories {  //只不过这种方式目前还在孵化阶段,可能会在未来某个版本移除
        maven {
            setUrl("https://maven.aliyun.com/repository/public/")
        }
    }
}

除了在settings.gradle.kts中配置三方仓库之外,我们更推荐在build.gradle.kts中对仓库进行配置。

与Maven一样,Gradle插件可以帮助我们在构建过程中实现各种各样的高级功能,它是用于增加和扩展任务(tasks)或构建逻辑的一种特殊类型的模块,在settings.gradle.kts 中可以配置插件,只不通常被用于需要对多个项目进行操作的插件,或者需要在构建开始之前进行一些配置的情况:

1
2
3
plugins {   //使用id明确插件名称,使用version中缀函数明确插件版本
    id("org.gradle.toolchains.fake") version "0.6.0"
}

对于多个子项目的大型项目而言,我们还需要在这里添加所有的子项目:

1
2
3
include("app")
include("business-logic")
include("data-model")

Settings

对象上还有更多属性和方法,可以使用它们来配置构建。虽然许多Gradle脚本通常以简短的Groovy或Kotlin语法编写,但设置脚本中的每个操作都是在Settings

对象上调用方法:

1
2
include("app")
settings.include("app")   //实际上是这样的,只是说settings可以省掉

编写构建脚本

项目的构建脚本build.gradle.kts文件:

对于设置文件中包含的每个项目,Gradle都会为其创建一个Project实例对象,我们可以直接在build.gradle.kts中使用,可以省略:

1
2
group = "cn.itbaima"
version = "1.0-SNAPSHOT"
1
2
project.group = "cn.itbaima"   //本质上就是project的属性
project.version = "1.0-SNAPSHOT"

在此对象中,包含以下常见属性:

姓名类型描述
nameString项目目录的名称。
pathString该项目的完全限定名称。
descriptionString该项目的描述。
dependenciesDependencyHandler配置项目的依赖列表。
repositoriesRepositoryHandler配置项目的依赖仓库。
layoutProjectLayout通过此对象来访问项目中的关键位置。
groupObject项目的组。
versionObject项目的版本。

整个项目所采用的插件像下面这样编写:

1
2
3
4
plugins {
    id("org.jetbrains.kotlin.jvm") version "1.9.0"
    id("application")
}

其中,插件application是由官方内置的插件,可以直接使用id()函数来选择,而上面的org.jetbrains.kotlin.jvm 插件没有被官方内置,需要我们手动使用version中缀函数指定其版本,这样Gradle才能在仓库中正确找到它。

一般情况下,我们普通的Java项目可以直接使用java插件,它能够直接完成编译和打包Java代码:

1
2
3
plugins {
    id("java")
}

也可以对这个插件进行一些配置,比如我们要生成目标的Java版本等:

1
2
3
4
configure<JavaPluginExtension> {
    targetCompatibility = JavaVersion.VERSION_17    //编译目标版本
    sourceCompatibility = JavaVersion.VERSION_17    //源代码版本(会导致只能使用对应版本具有的特性,如果使用更高版本的特性或语法将无法通过编译)
}

如果需要将项目打包为一个可执行的文件,也可以使用application插件,它包含java插件的全部功能,同样支持编译和打包Java代码,并且支持生成可执行的应用程序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
plugins {
    id("application")
}

java {   //configure<JavaApplication> 也可以直接写成 java 这个扩展函数,效果一样
    targetCompatibility = JavaVersion.VERSION_17
    sourceCompatibility = JavaVersion.VERSION_17
}

application {   //同configure<JavaApplication>
    mainClass = "com.test.Main"  //配置主类
}

配置完成后,我们直接执行build构建项目,生成文件

生成的压缩包的形式,我们可以直接解压,得到一系列文件:

bin目录中已经生成了用于运行我们Java项目的脚本(Mac/Linux是项目名称的脚本,Windows是bat脚本)然后在lib目录中是已经帮我们打包好的项目jar文件,如果我们项目中还存在其他的依赖,也会包含在这个lib中。我们可以直接运行:

配置项目中的依赖项,首先是对于依赖仓库的选择,默认情况下生成的代码选择的是Maven中心仓库:

1
2
3
4
repositories {
    mavenCentral()  //Maven中心仓库
    google()   //谷歌仓库
}

定义只使用本地仓库中的包:

1
2
3
repositories {
    mavenLocal()   //只使用Maven本地仓库中的软件包
}

我们可以通过maven函数来直接指定第三方仓库:

1
2
3
4
5
6
repositories {
    maven {
        //修改其url属性来指定一个第三方仓库地址
        setUrl("https://maven.aliyun.com/repository/public/")
    }
}

依赖在dependencies中进行编写,默认情况下添加了一些测试相关的依赖:

1
2
3
4
dependencies {
    testImplementation(platform("org.junit:junit-bom:5.9.1"))
    testImplementation("org.junit.jupiter:junit-jupiter")
}

使用两种不同的函数导入依赖:

  • implementation导入依赖,用于编译和运行生产代码。
  • testImplementation导入依赖,用于编译和运行测试代码。

字符串就是我们依赖的组、名称以及其对应的版本号:

  • org.junit:junit-bom:5.9.1对应的组为org.junit,依赖名称为:junit-bom,版本号为:5.9.1
1
2
//使用implementation来添加依赖
implementation("org.springframework:spring-context:6.1.3")

也可以分开进行编写,把组、名称和版本填写为三个参数:

1
implementation("org.springframework", "spring-context", "6.1.3")

一直使用最新版本的依赖,可以直接将版本设置为+来始终使用最新版本:

1
2
implementation("org.springframework:spring-context:+")
//生产环境下不推荐这样写,万一新版改了啥东西导致项目出大问题就得不偿失了

深入依赖配置

除了通过直接编写Maven坐标的形式来引入仓库中的依赖之外,我们也可以直接将一个本地的Jar包引入到项目中

1
2
3
4
5
6
7
8
9
dependencies {
    //使用files方法来指定一个文件进行导入
    implementation(
        files(
            "lib/spring-context-6.1.3.jar",
            "lib/spring-core-6.1.3.jar", "lib/spring-beans-6.1.3.jar", "lib/spring-jcl-6.1.3.jar"
        )
    )
}
1
2
3
public static void main(String[] args) {
    ApplicationContext context = new FileSystemXmlApplicationContext();
}

需要全部导入,可以直接使用fileTree方法来统一获取:

1
implementation(fileTree("lib"))  //直接引入lib下全部jar包

排除其中某些不需要的依赖或是与其他依赖冲突的依赖,可以对依赖进行排除操作:

1
2
3
4
implementation("org.springframework:spring-context:6.1.3") {
    //在Lambda中使用exclude来排除不需要的依赖
    exclude("org.springframework", "spring-aop")
}

强制设置软件版本

1
2
implementation("org.springframework:spring-context:6.1.3")
implementation("org.springframework:spring-aop:6.1.1")

此时我们的项目中不仅依靠spring-context引入了spring-aop的6.1.3版本,也手动引入了spring-aop 的6.1.1版本,此时Gradle会优先使用更新的版本作为依赖,所以这里实际引入使用的也是6.1.3版本。

如果我们执意要使用旧版本的依赖,可以通过上面的方式进行依赖的排除,或是给版本号添加感叹号表示强制使用:

1
2
3
4
implementation("org.springframework:spring-context:6.1.3") {
    exclude("org.springframework", "spring-aop")  //直接排除掉新版本的
}
implementation("org.springframework:spring-aop:6.1.1")
1
2
implementation("org.springframework:spring-context:6.1.3")
implementation("org.springframework:spring-aop:6.1.1!!")   //添加双感叹号

DependencyHandlerScope里面包含的方法:

  1. implementation: 用于添加项目的依赖项,这是最常用的方法。
  2. api: 与 implementation 类似,但它会暴露依赖项给项目的所有模块(多项目配置中讲解)
  3. compileOnly: 用于指定编译时依赖,但不会在运行时包含在最终构建结果中。
  4. testImplementation: 用于添加测试时需要的依赖项。
  5. androidTestImplementation: 用于添加Android测试时需要的依赖项。
  6. kapt: 用于添加 Kotlin 注解处理器依赖项。
  7. annotationProcessor: 用于添加 Java 注解处理器依赖项。
  8. runtimeOnly:仅在运行时使用,不用于编译

implementation是Java插件提供的方法,引入依赖,并且在打包之后也会附带引入的依赖:

compileOnly表示导入的依赖仅在编译时可用,编译完成后不会将依赖一起打包:

1
compileOnly("org.springframework:spring-context:6.1.3")

runtimeOnly,它表示导入的依赖仅在运行时可用,比如MySQL驱动这类我们不需要在项目中直接使用,但是在项目运行时又需要用到的依赖就非常适合:

1
runtimeOnly("org.springframework:spring-context:6.1.3")

自定义任务

Gradle可以在项目上完成的工作由一个或多个任务

定义,任务代表构建执行的某些独立工作单元,比如编译一些类,创建jar包,生成Javadoc文档,或将一些内容发布到代码仓库,这些任务通常由插件提供,我们在项目中引入插件后就可以直接执行对应的任务了,在不引入任何插件的情况下,只会包含一些内置的默认任务:

在引入java插件后,就出现了各种Java相关的任务,比如编译、构建、打包jar包等:

在执行某一个大任务的时候,就会执行一系列的任务,比如build命令,它不仅完成构建,还将所有的任务依赖关系进行完整的建立,可以看到:

在整个build任务执行的过程中,依次按顺序执行了各种各样的任务:

  1. compileJava:编译所有Java源代码文件。
  2. processResources:处理所有资源文件,由于这个项目没有资源文件,提示 NO-SOURCE

通过这一系列小任务,就完成了编译、构建、打包等一系列操作,只需要执行:./gradlew build 即可。

自定义添加build.gradle.kts中编写自定义任务:

注册任务需要使用registercreate函数来完成,一个最简单的任务可以像下面这样编写:

1
2
3
4
5
6
7
8
9
tasks.register("hello") {   //第一个参数为任务名称,第二个参数使用Lambda编写任务具体操作
    //任务包含一个完整的操作列表,我们需要传入对应的Action到队列中,这样就可以依次执行了
    doFirst {   //使用doFirst向任务队列首部插入新的Action,也就是要执行的内容
        println("我是自定义的任务开始")
    }
    doLast {   //向队列尾部插入Action
        println("我是自定义的任务结束")
    }
}

执行命令时可以添加额外的项目参数,在脚本中可以直接获取:

1
2
3
4
tasks.register("hello") {
    println("获取到自定义参数: ${project.properties["test"]}")
    ...
}

启动时通过-P传入参数

1
./gradlew hello -P test=hello

配置任务所属的组别,以及其它描述信息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
tasks.register("hello") {   //第一个参数为任务名称,第二个参数使用Lambda编写任务具体操作
    group = "build"//任务所属的组别
    description = "这是一个非常伟大的任务!"
    
    //任务包含一个完整的操作列表,我们需要传入对应的Action到队列中,这样就可以依次执行了
    doFirst {   //使用doFirst向任务队列首部插入新的Action,也就是要执行的内容
        println("我是自定义的任务开始")
    }
    doLast {   //向队列尾部插入Action
        println("我是自定义的任务结束")
    }
}

可以将一个任务作为gradle执行的默认任务,直接执行gradle命令就可以运行任务:

1
defaultTasks("hello")

一个任务还可以依赖于其他任务,比如我们的自定义任务在执行之前需要先完成源代码的编译操作:

1
2
3
4
5
6
tasks.register("hello") {
    group = "build"
    description = "这是一个非常伟大的任务!"
    dependsOn(tasks.compileJava)   //使用dependsOn函数来指定前置任务,可以是其他插件提供的,也可以是我们自己定义的,这个参数也可以传字符串
    ...
}

在执行时,会先完成前置任务之后再执行当前任务:

我们也可以让已经存在的任务来依赖我们的任务或是直接为其添加额外的操作:

1
2
3
4
5
6
7
8
tasks.named("build") {   //根据名称进行查找
    dependsOn("hello")   //直接配置依赖
    doLast { ... }   //添加新的Action到列表中
}

tasks.build {   //直接从tasks中获取,这仅限于插件提供的任务
    dependsOn("hello")
}

至此为任务build添加了前置任务到前置任务列表中:

在Gradle中,实际上所有的任务都是Task的子类,除了向上面这样直接编写Task类型,包括register等方法,默认也是在Lambda中为我们提供一个Task类型的对象:

1
2
3
@Override
TaskProvider<Task> register(String name, Action<? super Task> configurationAction) throws InvalidUserDataException;
//这里Action的默认提供类型就是Task

可以自行创建Task的子类,来编写自定义的任务类型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 继承 DefaultTask 类来创建一个自定义的 HelloTask 类,注意这个类必须要可继承,要么open要么直接抽象类
open class HelloTask : DefaultTask() {
    private var name: String = ""

    @TaskAction   //添加@TaskAction注解来声明此函数为任务的Action
    fun hello() {
        println("${name}: 卡布奇诺今犹在,不见当年倒茶人")
    }

    fun user(name: String) {   //类中也可以具有其他的函数
        this.name = name
    }
}

tasks.register<HelloTask>("hello") {   //使用register时指明我们自定义的任务类
    user("卢本伟")   //此时this就是HelloTask类型了,我们可以直接使用自定义的函数
}

除了通过插件或是我们自定义的形式编写任务之外,Gradle也为我们提供了一些内置的任务类型,这些任务通常是一些经常会用到的操作

1
2
3
4
5
tasks.register<Copy>("hello") {   //这里使用Copy类型
    from("build/classes")   //使用from和into设置复制的目录和目标位置
    into("test")
    dependsOn(tasks.build)   //依赖一下build
}

执行我们自定义的hello任务,就可以完成构建 + 拷贝了:

除了Copy操作,开发人员还可以利用许多任务类型,包括GroovyDocZipJarJacocoReportSignDelete等。

生命周期钩子

有些时候我们希望在Gradle的整个生命周期中的不同时段执行一些操作,我们可以使用官方提供的生命周期钩子函数。

  1. 构建初始阶段
    • gradle.settingsEvaluated() 完成项目的配置阶段之后调用(只能定义在 setting.gradle 或 init.gradle 脚本中)
    • gradle.projectsLoaded() 所有项目加载之后调用(只能定义在 setting.gradle 或 init.gradle 脚本中)
  2. 配置阶段
    • gradle.beforeProject() 每个项目完成配置之前调用(只能定义在 setting.gradle 或 init.gradle 脚本中)
    • gradle.afterProject() 每个项目完成配置之后调用
    • gradle.projectEvaluated() 所有项目全部完成配置之后调用
    • gradle.afterEvaluate() 整个配置阶段完成后调用
    • gradle.taskGraph.whenReady 全部任务图已经构建完成可以就绪后调用
  3. 执行阶段
    • gradle.taskGraph.beforeTask 执行每一个任务之前调用
    • gradle.taskGraph.afterTask 每一个任务执行完成之后调用
    • gradle.buildFinished 整个构建全部结束后调用

使用示例:

1
2
3
4
5
6
7
gradle.settingsEvaluated {
    println("开始构建")
}

gradle.buildFinished {
    println("构建结束")
}

可以在不同的阶段执行自定义的内容了,可以利用这种特性来统计某一个阶段或是任务耗费的时间:

1
2
3
4
5
6
7
8
9
var time: Long = 0
gradle.taskGraph.beforeTask {
    time = System.currentTimeMillis()
}

gradle.taskGraph.afterTask {
    val takeTime = System.currentTimeMillis() - time
    println("任务: $name 执行耗时: ${takeTime}ms")
}

项目发布

可以将Gradle项目发布到的Maven仓库中,以发布到本地仓库为例:

1
2
3
4
plugins {
    id("java")
    id("maven-publish")   //首先引入maven-publish插件
}

配置发布相关的内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
publishing {
    publications {
        create<MavenPublication>("library") {   //发布的相关信息Maven坐标信息
            groupId = "cn.itbaima"
            artifactId = "hello"
            version = "0.0.1"
            from(components["java"])   //发布为jar包形式
        }
    }

    repositories {
        mavenLocal()    //指定为本地Maven仓库
    }
}

执行publish任务即可发布项目到本地仓库:

创建SpringBoot项目

自动生成了项目的build.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
plugins {   //首先是插件配置
    java  //最基础的Java插件
    id("org.springframework.boot") version "3.2.1"   //SpringBoot插件
    id("io.spring.dependency-management") version "1.1.4"   //Spring依赖管理插件
}

group = "cn.itbaima"
version = "0.0.1-SNAPSHOT"

java {
    sourceCompatibility = JavaVersion.VERSION_17   //配置Java源代码编译版本
}

configurations {
    compileOnly {   //注解处理相关配置,用于Lombok
        extendsFrom(configurations.annotationProcessor.get())
    }
}

repositories {   //使用Maven中心仓库
    mavenCentral()
}

dependencies {   //包含SpringBoot相关依赖,以及Lombok依赖
    implementation("org.springframework.boot:spring-boot-starter-web")
    compileOnly("org.projectlombok:lombok")
    annotationProcessor("org.projectlombok:lombok")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.withType<Test> {
    useJUnitPlatform()
}

JVM语言混合编程

1
2
3
4
plugins {   //同时配置Java和Kotlin/JVM插件
    id("java")
    kotlin("jvm") version "1.9.21"
}

编写好插件后刷新项目,接着就可以在src下的main和test目录中同时创建两个源代码目录:

  • java:存放Java源代码文件
  • kotlin:存放Kotlin源代码文件
1
2
3
4
5
//位置:src/main/kotlin/com/test/Student.kt

package com.test

data class Student(var name: String, var age: Int)
1
2
3
4
5
6
7
8
9
//位置:src/main/java/com/test/Main.java
package com.test;

public class Main {
    public static void main(String[] args) {
        Student student = new Student("小明", 18);
        System.out.println(student);
    }
}

如果要打包为可执行的应用,也可以直接使用application插件:

1
2
3
4
plugins {
    id("application")
    kotlin("jvm") version "1.9.21"
}

打包之后,已经自动将Kotlin标准库依赖集成了,也可以直接使用:

多项目配置

前面我们介绍的是单个项目的布局,现在我们接着来看多个项目的布局。

通常对于一些大型的分布式项目来说,我们会在一个项目中包含多个模块,不同的模块负责不同的功能,并且不同模块的代码独立进行编写,这时整个Gradle项目就会存在多个子项目:

1
2
3
4
5
6
7
8
9
large - project
|
├── auth - service
│   ...
│   └── build.gradle.kts
├── chat - service
│   ...
│   └── build.gradle.kts
└── settings.gradle.kts

可以看到,在根项目下,只存在settings.gradle.kts用于全局配置,而具体的build.gradle.kts构建文件存在于各个子项目中分别进行编写。

项目创建

创建完成后,settings.gradle.kts文件,IDEA已经自动添加了模块引入:

1
2
rootProject.name = "large-project"
include("auth-service")

build.gradle.kts现在由子模块进行配置,每个子模块都可以单独处理。

在存在多个子项目的时候,我们可以直接在根项目中执行指令,会生效于全部的子项目:

可以看到,这里的项目名称前面都加了一个:符号。

我们也可以配置项目之间的依赖,我们只需在dependencies中进行配置:

1
2
3
4
5
dependencies {
    implementation(project(":common"))   //使用project方法来引用其他项目作为依赖,项目名称前需要添加单引号
    testImplementation(platform("org.junit:junit-bom:5.9.1"))
    testImplementation("org.junit.jupiter:junit-jupiter")
}

将一些部分统一进行配置,比如配置代码仓库地址,以及项目用到的插件,直接在根项目创建一个build.gradle.kts来统一编写内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
subprojects {   //subprojects表示对所有的子项目生效
    apply(plugin = "java")   //定义插件需要使用apply来完成,plugin{}在这里不行

    group = "cn.itbaima"   //定义组
    version = "unspecified"  //定义版本

    repositories {   //定义自定义仓库地址
        maven {
            setUrl("https://maven.aliyun.com/repository/public/")
        }
    }
}

即使不在子项目中编写这些,就可以直接得到根项目的配置:

1
2
3
4
5
6
7
8
dependencies {
    testImplementation(platform("org.junit:junit-bom:5.9.1"))
    testImplementation("org.junit.jupiter:junit-jupiter")
}

tasks.test {
    useJUnitPlatform()
}

依赖的传递

我们在使用多模块时可能会遇到这样的一个问题,现在有三个模块,并且具有以下依赖关联:

  • service-a
  • service-b implementation(service-a)
  • service-c implementation(service-b)

此时,从链式的依赖关系上来看,它们长这样:service-a -> service-b -> service-c

按照正常的传递关系来说,在B中应该是可以直接使用A中定义的内容的,因为依赖了A模块,同时,由于C依赖了B模块,那么理所应当,C也应该可以直接使用A中定义的内容,我们来看看是否真的如此:

1
2
3
4
5
6
7
//A模块中定义
public class Test {
    public static void test()
    {
        System.out.println("Hello World!");
    }
}
1
2
3
4
5
6
//B模块中定义
public class Main {
    public static void main(String[] args) {
        Test.test();
    }
}
1
2
3
4
5
6
//C模块中定义
public class Main {
    public static void main(String[] args) {
        Test.test();   //找不到类Test,编译失败
    }
}

因为implementation不支持依赖传递,因此,即使我们改变了B模块的依赖,此时C也无法通过传递的形式得到B包含的依赖,由于不需要处理传递依赖,在编译时只需要处理一层,因此速度会非常快,大部分情况下非常推荐使用implementation 来进行依赖导入。

如果需要实现传递依赖的效果,我们需要使用另一个插件提供的方法来导入依赖:

1
2
3
4
5
6
7
8
9
plugins {
    `java-library`   //java-library提供了传递依赖api函数
}

dependencies {
    api(project(":common"))   //与implementation效果一样,但是支持传递依赖
    testImplementation(platform("org.junit:junit-bom:5.9.1"))
    testImplementation("org.junit.jupiter:junit-jupiter")
}

此时,模块B的依赖由于使用了api进行导入,我们在模块C中无论是使用api导入还是implementation导入B,都可以使用B中api函数导入的依赖了。

使用buildSrc模块

在Gradle中有一个特殊机制,可以创建一个名为buildSrc的模块,支持自定义能够在kts脚本中使用的内容。

注意在创建之后需要移除settings.gradle.kts中的包含关系:

1
2
3
rootProject.name = "large-project"
include("auth-service", "chat-service", "common")
include("buildSrc")   //不能作为子项目存在

修改一下build.gradle.kts文件:

1
2
3
4
5
6
7
plugins {
    `kotlin-dsl`
}

repositories {
    mavenCentral()
}

如我们希望吧阿里云Maven仓库封装成一个函数的形式,直接使用,就像mavenCentral(),直接开一个新的kt文件编写扩展函数:

1
2
3
4
5
6
7
import org.gradle.api.artifacts.dsl.RepositoryHandler

//由于repositories函数中提供的this为RepositoryHandler类型的对象
//这里我们直接为其编写扩展函数
fun RepositoryHandler.mavenAlibaba() = maven {
    setUrl("https://maven.aliyun.com/repository/public/")
}

可以在其他的kts脚本中使用我们定义的内容了,比如根项目的配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
subprojects {
    apply(plugin = "java")

    group = "cn.itbaima"
    version = "unspecified"

    repositories {
        mavenAlibaba()   //直接使用自定义的
    }
}

也可以使用这种方式定义所有需要使用的依赖版本,实现统一管理,比如编译版本的管理:

1
2
3
4
5
6
import org.gradle.api.JavaVersion

object Version {
    val sourceVersion = JavaVersion.VERSION_17
    val targetVersion = JavaVersion.VERSION_17
}
1
2
3
4
5
6
7
8
subprojects {
    ...

    configure<JavaPluginExtension> {
        targetCompatibility = Version.targetVersion   //直接使用Version类获取版本
        sourceCompatibility = Version.sourceVersion
    }
}

需要使用一些第三方的依赖,也可以统一管理版本:

1
2
3
4
object Version {
    ...
    val springVersion = "6.1.3"   //统一Spring相关依赖的版本
}
1
2
3
4
5
6
7
dependencies {
    implementation("org.springframework:spring-beans:${Version.springVersion}")   //直接从Version中获取版本
    implementation("org.springframework:spring-aop:${Version.springVersion}")
    implementation("org.springframework:spring-web:${Version.springVersion}")
    testImplementation(platform("org.junit:junit-bom:5.9.1"))
    testImplementation("org.junit.jupiter:junit-jupiter")
}

除了以上用法之外,我们也可以在buildSrc模块中编写自定义的插件并使用,这里我们创建一个新的插件类:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package com.plugin

import org.gradle.api.Plugin
import org.gradle.api.Project

class MyPlugin : Plugin<Project> {
    override fun apply(target: Project) {   //插件应用时会直接调用apply函数
        println("插件生效了")
    }
}

接着我们需要在buildSrc的构建脚本中声明此插件:

1
2
3
4
5
6
7
8
gradlePlugin {
    plugins {
        create("my-custom-plugin") {   //创建一个新的Plugin
            id = "my-plugin"  //插件的ID
            implementationClass = "com.plugin.MyPlugin"   //插件的实现类
        }
    }
}

声明之后,我们就可以在项目中使用了:

1
2
3
4
plugins {
    ...
    id("my-plugin")   //直接通过id使用插件
}

利用插件,我们可以快速为项目添加各种任务,这里我们魔改一下上面写的插件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class MyPlugin : Plugin<Project> {
    override fun apply(target: Project) {  //直接在apply中注册新的任务,这样插件加载之后就具有这些任务了
        target.tasks.register("a") {
            doLast { println("你干嘛") }
        }

        target.tasks.register("b") {
            doLast { println("哎哟") }
            dependsOn(target.tasks.named("a"))
        }
    }
}

此时项目中已经存在我们自定义的两个任务了