Gradle 是一种开源构建自动化工具,可以构建几乎任何类型的软件。Gradle 对您要构建什么或如何构建它几乎没有任何假设。这使得 Gradle 特别灵活。Gradle 构建脚本是使用 Groovy 或 Kotlin DSL 编写的。在深入了解 Gradle 的细节之前,了解以下术语会很有帮助。
- 官网地址:https://www.gradle.org/
- 插件配置:http://plugins.gradle.org
- 官网文档:https://docs.gradle.org/
常见的构建工具主要有三种:Ant、Maven、Gradle。Ant现在使用的不是太多,至少在国内是这样。如果对Maven已经非常熟悉了,可能不太愿意使用gradle,感觉貌似没有必要。但是既然gradle出现了就说明有很多人对Maven还是有一定的意见。因此在这里我来总结一下gradle相比maven的优势。
下图来源于官网:
也许是因为我上面说的原因,也许有其他原因,不得不承认的一件事情就是gradle作为一个新兴的工具已经有了广泛的应用。spring等项目已经从Maven切换到了gradle。开发安卓程序也只支持gradle了。因此不管是否现在需要将项目从maven切换到gradle,但是至少学习gradle是一件必要的事情。
安装 Gradle 之前首先要确保我们已经在系统中安装了 JDK,并且JDK的版本最低在1.7。最新的Gradle版本需要JDK1.8及以上,官网文档地址:https://docs.gradle.org/,详细可参考官方文档。如果是Java项目建议直接采用IDE方式安装,比如IntelliJ IDEA,IDEA不仅自带了Maven构建工具,还可以在创建项目的时候选择Gradle创建项目。
同Maven一样,安装成功后或是创建了一个Gradle项目后,一般会在当前系统用户的根目录下创建仓库。如下图所示,其中/Users/liudong/.gradle 就是Gradle的默认本地仓库地址,最初时没有gradle.properties和init.gradle这两个文件,需要手动添加,这两个文件就是全局配置文件。但建议每到一个公司就修改一个目录(习惯问题,可按个人习惯决定是否分开)
在gradle中配置下载镜像需要在/Users/liudong/.gradle文件夹中新建一个init.gradle初始化脚本,这样一来,gradle下载镜像的时候就会使用这里配置的镜像源下载,速度会快很多,脚本文件内容如下:
allprojects {
repositories {
maven {
url "https://maven.aliyun.com/repository/public"
}
maven {
url "https://maven.aliyun.com/repository/jcenter"
}
maven {
url "https://maven.aliyun.com/repository/spring"
}
maven {
url "https://maven.aliyun.com/repository/spring-plugin"
}
maven {
url "https://maven.aliyun.com/repository/gradle-plugin"
}
maven {
url "https://maven.aliyun.com/repository/google"
}
maven {
url "https://maven.aliyun.com/repository/grails-core"
}
maven {
url "https://maven.aliyun.com/repository/apache-snapshots"
}
}
}
在/Users/liudong/.gradle文件夹中直接新建一个gradle.properties文件,创建内容不复杂,直接看代码即可,设置Gradle的JVM参数、代理和日志等信息。更多的设置可查看官网。
org.gradle.jvmargs=-Xmx4g -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
systemProp.http.proxyHost=127.0.0.1
systemProp.http.proxyPort=10800
systemProp.https.proxyHost=127.0.0.1
systemProp.https.proxyPort=10800
systemProp.file.encoding=UTF-8
org.gradle.warning.mode=all
myProjectProp=HelloApp
这些属性是可以在buid.gradle.kts中用命令取到的,比如:
if (hasProperty("myProjectProp")) {
// Accessing the value, throws if not present
println(property("myProjectProp"))
}
OS:MacOS Monterey 12.6.5
idea版本:IntelliJ IDEA 2023.1.1 (Ultimate),不同idea版本截图略有差异。
可以【create new project】新建项目,或在已有项目上【create new module】新建模块。建议使用Idea开发时,在源码的管理上可以先创建一个【Empty Project】,然后以module的方式引入源码。这样方便管理,不同的模块也可以有不同的配置方式。下图是在已有项目上创建了一个新模块。
Idea创建模块时所有设置就一个界面,详细解释如下,与构建项目相关的关键信息已标红:
配置好后,点击【创建】会生成如下图所示目录结构:
点开IDEA的【首选项】,找到如下图所示的配置页面,按需修改红框内的内容即可:
可以项目根路径下同样配置一个名为gradle.properties的文件,大体配置如下:
#性能配置
kotlin.incremental.useClasspathSnapshot = false
kotlin.stdlib.default.dependency = false
org.gradle.configuration-cache = true
org.gradle.caching = true
systemProp.org.gradle.unsafe.kotlin.assignment = true
其它几个常用的配置如下:
java
plugins {
id("java")
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
tasks {
withType {
sourceCompatibility = "17"
targetCompatibility = "17"
}
}
kotlin
plugins {
id("java")
id("org.jetbrains.kotlin.jvm") version "1.8.21"
}
java {
sourceCompatibility = JavaVersion.VERSION_17
}
tasks {
compileKotlin {
kotlinOptions.jvmTarget = "17"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "17"
}
}
java2kotlin
选择特定的.java文件后,选择菜单栏【code】-【convert java to kotlin】
kotlin2java
选择特定的.kt文件后,选择菜单栏【tool】-【kotlin】-【show kotlin bytecode】,在弹出窗口上选择【反编译】,如下图:
可以称Gradle Wrapper为Gradle包装器,是将Gradle再次包装。让所有的Gradle构建方法在 Gradle 包装器的帮助下运行。目的是可以让我们不需要在电脑中安装 Gradle 环境也可以运行 Gradle 项目。官方建议任何 Gradle 构建方法在 Gradle Wrapper帮助下运行,利于多人协同开发时环境的统一。
#Gradle根目录,默认根目录是/Users/userName/.gradle/;
distributionBase=GRADLE_USER_HOME
#distributionBase+distributionPath就是Gradle解包后的存放的具体目录
distributionPath=wrapper/dists
#Gradle项目中用到的Gradle版本,如果你使用IDEA的话推荐下载all版,这样可以分析源代码进而提供更加精确的gradle脚本支持
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip
#Gradle压缩包下载后存储父目录
zipStoreBase=GRADLE_USER_HOME
#zipStoreBase+zipStorePath就是 Gradle 压缩包的存放位置
zipStorePath=wrapper/dists
Gradle和Maven一样,有两种执行方式,依拖相关的IDE提供的图形化界面或是命令行;
命令都比较简单,在IDEA右侧选择相应的Gradle项目,所有可执行的命令都在Tasks下面,双击即可。最下面的【运行配置】是一个快捷操作,保存了最近操作过的命令。
因为采用的是Gradle Wrapper模式,电脑中并没有安装 Gradle 环境(其实也不建议安装),所以原始的Terminal并没gradle这个命令,需要借助项目根目录下的gradlew(linux和mac) 和 gradlew.bat(windows)文件。首先打开命令行切换到当前项目的根目录下,然后执行相关的命令:./gradlew xxxx 。
(base) MacBook:gradleLession liudong$ ./gradlew -v
------------------------------------------------------------
Gradle 8.0
------------------------------------------------------------
Build time: 2023-02-13 13:15:21 UTC
Revision: 62ab9b7c7f884426cf79fbedcf07658b2dbe9e97
Kotlin: 1.8.10
Groovy: 3.0.13
Ant: Apache Ant(TM) version 1.10.11 compiled on July 10 2021
JVM: 1.8.0_144 (Oracle Corporation 25.144-b01)
OS: Mac OS X 10.16 x86_64
(base) MacBook:gradleLession liudong$ ./gradlew -q projects
------------------------------------------------------------
Root project 'gradleLession'
------------------------------------------------------------
Root project 'gradleLession'
No sub-projects
To see a list of the tasks of a project, run gradlew :tasks
For example, try running gradlew :tasks
(base) MacBook:gradleLession liudong$ ./gradlew dependencies
> Task :dependencies
------------------------------------------------------------
Root project 'gradleLession'
------------------------------------------------------------
annotationProcessor - Annotation processors and their dependencies for source set 'main'.
No dependencies
compileClasspath - Compile classpath for source set 'main'.
+--- org.projectlombok:lombok:1.18.26
\--- mysql:mysql-connector-java:8.0.32
\--- com.mysql:mysql-connector-j:8.0.32
\--- com.google.protobuf:protobuf-java:3.21.9
...省略
A web-based, searchable dependency report is available by adding the --scan option.
BUILD SUCCESSFUL in 531ms
1 actionable task: 1 executed
./gradlew -q dependencies api:dependencies
./gradlew -q api:dependencies --configuration testCompile
gradle -q webapp:dependencyInsight --dependency groovy --configuration compile
org.codehaus.groovy:groovy-all:2.2.0
— project :api
— compile
\> ./gradlew -q api:properties
\------------------------------------------------------------
Project :api - The shared API for the application
\------------------------------------------------------------
allprojects: [project ':api']
ant: org.gradle.api.internal.project.DefaultAntBuilder@12345
antBuilderFactory: org.gradle.api.internal.project.DefaultAntBuilderFactory@12345
artifacts: org.gradle.api.internal.artifacts.dsl.DefaultArtifactHandler@12345
asDynamicObject: org.gradle.api.internal.ExtensibleDynamicObject@12345
buildDir: /home/user/gradle/samples/userguide/tutorial/projectReports/api/build
buildFile: /home/user/gradle/samples/userguide/tutorial/projectReports/api/build.gradle
(base) MacBook:gradleLession liudong$ ./gradlew task
> Task :tasks
------------------------------------------------------------
Tasks runnable from root project 'gradleLession'
------------------------------------------------------------
Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles main classes.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the main classes.
testClasses - Assembles test classes.
...省略
//buid.gradle中定义的Task,mTest
task mTest{
doLast{
println "这是测试Task"
}
}
//在控制台执行命令排除
$ gradle clean build -x mTest
分析项目构建过程,主要是一些耗时等信息。我们看到控制台它会输出已生成 HTML 格式和 XML 格式的文档。
(base) MacBook:gradleLession liudong$ ./gradlew --profile build
BUILD SUCCESSFUL in 1s
2 actionable tasks: 2 executed
See the profiling report at: file:///Users/liudong/ideaWS/ideaplugin/gradleLession/build/reports/profile/profile-2023-05-12-22-30-15.html
A fine-grained performance profile is available: use the --scan option.
上图中第6行就是生成的报告的地址,也可以在项目根目录下的build/reports/profile目录下找到报告的.html文件,打开后如下图所示:
引入打jar包插件
如果想打war包,只面引入以下插件即可:
apply plugin: 'war'
引入打war包插件
如果想打war包,只面引入以下插件即可:
apply plugin: 'war'
打包
编译并打包 jar 文件,但不会执行单元测试,用的是assemble命令。一些其他插件可能会增强这个任务的功能。例如,如果采用了 War 插件,这个任务便会为你的项目打出 War 包。
// 编译并打Debug包
$ gradle assembleDebug
// 编译app module 并打Debug包
$ gradlew install app:assembleDebug
// 编译并打Release的包
# gradle assembleRelease
// 编译并打Release包并安装
$ gradle installRelease
// 卸载Release包
$ gradle uninstallRelease
指定main.class
指定应用是一个app,然后指定Hello为main.java。
plugins {
id("application")
}
application {
mainClass.set("com.example.Hello")
}
把依赖打到jar包中
tasks {
// 将依赖打进jar包中
jar.configure {
duplicatesStrategy = org.gradle.api.file.DuplicatesStrategy.INCLUDE
from(configurations.runtimeClasspath.get().filter { it.name.endsWith("jar")}.map { zipTree(it) })
}
}
需要修改build.gradle文件,详细代码如下,然后执行右侧 publishing 命令。
plugins {
id 'java-library'
id 'maven-publish'
}
publishing {
publications {
myLibrary(MavenPublication) {
from components.java
}
}
repositories {
maven {
// default credentials for a nexus repository manager
credentials {
username 'admin'
password 'xxxxx'
}
allowInsecureProtocol = true //允许http
// 发布maven存储库的url
url "http://localhost:8080/nexus/content/repositories/releases/"
}
}
}
gradle -Dorg.gradle.logging.level=warn clean
-Dorg.gradle.logging.level=(quiet,warn,lifecycle,info,debug)
gradle 构建的生命周期主要分为三个阶段,Initialization,Configuration,Execution。
大体的配置如下所示:
plugins {
id 'org.springframework.boot' version '2.7.4'
id 'java'
}
group = 'com.zd'
version = '1.0-SNAPSHOT'
repositories {
mavenLocal()
maven { url "https://maven.aliyun.com/nexus/content/repositories/central/" }
mavenCentral()
}
dependencies {
testImplementation 'junit:junit:4.13'
implementation 'org.projectlombok:lombok:1.18.26'
implementation 'mysql:mysql-connector-java:8.0.32'
}
buildscript {
ext {
springBootVersion = "2.3.3.RELEASE"
}
repositories {
mavenLocal()
maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
jcenter()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
和发布打包相关的一些配置。
group = 'com.zd'
version = '1.0-SNAPSHOT'
gradle插件类似于maven插件,plugins闭包(标签)里的插件必须是gradle官方插件库(http://plugins.gradle.org)里的,另外plugins块不能放在多项目配置块(allProjects, subProjects)里。另外因为java是核心插件,所以不用指定版本,而其它的插件比如org.springframework.boot是社区插件,所以必须指定版本。
plugins {
id 'org.springframework.boot' version '2.7.4'
id 'java'
}
buildscript中的声明是gradle脚本自身需要使用的资源。可以声明的资源包括依赖项、第三方插件、maven仓库地址等。而在build.gradle文件中直接声明的依赖项、仓库地址等信息是项目自身需要的资源。gradle在执行脚本时,会优先执行buildscript代码块中的内容,然后才会执行剩余的build脚本。
buildscript代码块中的repositories和dependencies的使用方式与直接在build.gradle文件中的使用方式几乎完全一样。唯一不同之处是在buildscript代码块中你可以对dependencies使用classpath声明。该classpath声明说明了在执行其余的build脚本时,class loader可以使用这些你提供的依赖项。这也正是我们使用buildscript代码块的目的。某种意义上来说,classpath 声明的依赖,不会编译到最终的jar包里面
另外,buildscript必须位于plugins块和apply plugin之前
buildscript {
ext {
springBootVersion = "2.3.3.RELEASE"
}
repositories {
mavenLocal()
maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
jcenter()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
注意上面代码中的ext代码块,ext代码块是提供给用户自定义属性用的,一般用来定义版本,实现版本的集中管理。
gradle仓库可以直接使用maven的仓库,但是gradle下载的jar包文件格式与maven不一样,所以不能和maven本地仓库共用,仓库的配置,是在repository中的:
repositories {
mavenLocal() //本地仓库
maven { url 'http://maven.aliyun.com/nexus/content/groups/public' } //外部仓库(阿里云)
mavenCentral() // maven 中心仓库
}
gradle依赖写在dependcies代码块中,gradle中引入依赖只需要一行,遵循scope 'gropId:artifactId:version'
的格式,也可以是scope (gropId:artifactId:version)
dependencies {
testImplementation 'junit:junit:4.13'
implementation 'org.projectlombok:lombok:1.18.26'
implementation 'mysql:mysql-connector-java:8.0.32'
}
dependencies {
testImplementation('junit:junit:4.13')
implementation('org.projectlombok:lombok:1.18.26')
implementation('mysql:mysql-connector-java:8.0.32')
}
maven只有compile、provided、test、runtime,而gradle有以下几种scope:
id 'java-library'
== (在旧版本中作用与compile相同,新版本移除了compile)使用api配置的依赖会将对应的依赖添加到编译路径,并将依赖打包输出,但是这个依赖是可以传递的,比如模块A依赖模块B,B依赖库C,模块B在编译时能够访问到库C,但是与implemetation不同的是,在模块A中库C也是可以访问的。我们开发中,可能大家或多或少都会遇到 jar 包冲突的问题,有时候两个 jar 包不同,但是里面有两个类的包名路径是一摸一样的。这样我们就需要排除掉某个包中的重复的类,这时候就需要用的 exclude 命令,如下例子:
compile('org.hibernate:hibernate:3.1') {
//以artifact name来排除出
exclude module: 'cglib'
//通过group name来排除
exclude group: 'org.jmock'
}
configurations {
runtime.exclude group: "org.slf4j", module: "slf4j-log4j12"
compile.exclude group: "org.slf4j", module: "slf4j-log4j12"
}
configurations.all {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
}
按下图创建一个新的项目,注意要选择【新建项目】,然后删除项目中的src文件夹。
如下图所示,注意下图中的父项要选b2bdemo。
经过以上两部后,项目的目录情况如下图所示:
统一由settings.gradle文件管理,配置文件如下所示,其它文件没有特殊的地方,注意groovy和kts的写法,一般就是差个(),然后把'改成":
rootProject.name = 'b2bdemo'
include 'api'
include 'server'
rootProject.name = "b2bdemo"
include("api")
另外可以有更多的配置管理,比如:
include("project-a")
project(":project-a").projectDir = file("../my-project-a")
project(":project-a").buildFileName = "project-a.gradle"
build.gradle配置
allprojects{ // 所有模块的配置
group = 'org.example'
version = '0.0.1'
repositories {
mavenLocal()
maven { url 'https://maven.aliyun.com/repository/public/' }
mavenCentral()
}
//指定编码格式
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
//这里的clean它是一个 Gradle 任务,它继承自Delete,执行clean命令时删除build文件夹下的内容。
task clean(type: Delete) {
delete rootProject.buildDir
}
}
subprojects { //子模块配置
apply plugin: "java"
apply plugin: 'java-library'
apply plugin: "idea" //指定编辑器
//java版本
sourceCompatibility = 1.8
targetCompatibility = 1.8
//公共依赖
dependencies {
compileOnly 'org.projectlombok:lombok:1.18.20'
annotationProcessor 'org.projectlombok:lombok:1.18.20'
}
}
在7.0+版本,gradle提供了dependencyResolutionManagement来进行多模块之间的依赖共享,类似于maven的dependencyManagement:
第一步:
在7.4以下版本启用该功能,得在
settings.gradle
中添加enableFeaturePreview('VERSION_CATALOGS')
,在settings.gradle
中添加:
dependencyResolutionManagement{
versionCatalogs{
libs{ //libs名字可以任意取,最好为libs,到8.0可能会强制使用libs
library('hutool-core','cn.hutool:hutool-core:5.8.6')
//library在7.4以下版本为alias在7.4+版本被弃用,并且alias用法与library略有区别,详情参考文末链接
}
}
}
l ibrary第一个字符串为依赖的别名,注意别名必须由一系列以破折号(-
,推荐)、下划线 (_
) 或点 (.
) 分隔的标识符组成(当然,不写分隔符也可以)。标识符本身必须由ascii
字符组成,最好是小写,最后是数字。
第二步:
之后,便可以在子模块build.gradle中使用:
dependencies {
implementation(libs.hutool.core)
}
如果几个依赖共用一个版本,可以使用version来统一管理版本(注意,library中groupid与artifactid用‘,’隔开了),dependencies中用法一致
dependencyResolutionManagement{
versionCatalogs{
libs{
version('hutool','5.8.6')
library('hutool-core','cn.hutool','hutool-core').versionRef('hutool')
library('hutool-http','cn.hutool','hutool-http').versionRef('hutool')
library('hutool-json','cn.hutool','hutool-json').versionRef('hutool')
}
}
}
要是不想一个个引用这几依赖的话,还可以使用bundles将几个依赖绑定到一起,一次性引入多个依赖:
dependencyResolutionManagement{
versionCatalogs{
libs{
version('hutool','5.8.6')
library('hutool-core','cn.hutool','hutool-core').versionRef('hutool')
library('hutool-http','cn.hutool','hutool-http').versionRef('hutool')
library('hutool-json','cn.hutool','hutool-json').versionRef('hutool')
bundle('hutool',['hutool-core','hutool-http','hutool-json'])
}
}
}
在子模块的build.gradle中的dependencies中:
dependencies {
implementation(libs.bundles.hutool)
}
dependencyResolutionManagement{
versionCatalogs{
libs{
plugin('spring-dependency','io.spring.dependency-management').version('1.0.14.RELEASE')
}
}
}
只需要在dependencies中按照如下格式依赖即可,并且也是遵循依赖引入的几种scope规则的,要注意的是,被引用项目的类必须在软件包下,才可以被找到,下例代码中的:server为一个项目模块。
dependencies {
testImplementation platform('org.junit:junit-bom:5.9.1')
testImplementation 'org.junit.jupiter:junit-jupiter'
implementation(':server')
}
implementation(project(":server"))
在setting.gradle.kts中配置,
rootProject.name = "my-composite"
includeBuild("my-app")
includeBuild("my-utils")
然后在子模块中配置task,这样在构建子模块之前会先执行模块my-app的任务。
tasks.register("run") {
dependsOn(gradle.includedBuild("my-app").task(":app:run"))
}
一般定义在build.gradlw.kts文件中。详细参考文件处理
自定义的任务会显示在idea右侧工具栏的other标签中,双击就可以运行。
tasks.register("testBoth") {
doFirst {
println("This is executed first during the execution phase.")
}
doLast {
println("This is executed last during the execution phase.")
}
println("This is executed during the configuration phase as well, because :testBoth is used in the build.")
}
//gradle -q testBoth
执行顺序是 println, first, last, -q表示不打印日志
这里的Copy是Gradle插件带的一些快捷工具,下面代码表示使用Copy工具定义一个名为copyReportsDirForArchiving的任务,复制文件。类似的还有zip、delete等。
tasks.register("copyReportsDirForArchiving") {
from(layout.buildDirectory.dir("reports"))
into(layout.buildDirectory.dir("toArchive"))
}
tasks.register("cleanOutDir") {
dependsOn(":clean")
delete(rootProject.buildDir.path + "/../out")
}
// demo.gradle
task showProjectName{
doLast {
println("$project.name")
}
}
showProjectName.enabled = false//禁用任务
// demo1.gradle
apply from: '../demo.gradle'
//运行,使用任务全拼
$ gradle showProjectName
一般在build.gradle.kts中做如下配置,同时检查下Gradle配置
compileOnly("org.projectlombok:lombok:1.18.26")
annotationProcessor("org.projectlombok:lombok:1.18.26")
testCompileOnly("org.projectlombok:lombok:1.18.26")
testAnnotationProcessor("org.projectlombok:lombok:1.18.26")
如果还不行再检查下以下配置(正常来讲不需要做如下的改动,具体原因没查,需要试):
# lombok.config
# 声明该配置文件是一个根配置文件,从该配置文件所在的目录开始扫描
config.stopBubbling=true
# 全局配置 equalsAndHashCode 的 callSuper 属性为true
lombok.equalsAndHashCode.callSuper=call