四、项目结构管理
4.1 用户目录
用户目录就是运行中的gradle项目依赖文件的存储位置,类似maven的本地仓库。用户目录位置:${user_home}/.gradle
目录结构
├── caches #全局缓存
│ ├── 4.8 #特定版本的缓存
│ ├── 4.9 #特定版本的缓存
│ ├── ⋮
│ ├── jars-3 #共享缓存,依赖包
│ └── modules-2 #共享缓存,依赖包
├── daemon #gradle daemon的注册表和日志
│ ├── ⋮
│ ├── 4.8
│ └── 4.9
├── init.d #全局初始化脚本
│ └── my-setup.gradle
├── jdks #toolchain support下载的jdk
│ ├── ⋮
│ └── jdk-14.0.2+12
├── wrapper
│ └── dists #gradle warrper下载的文件
│ ├── ⋮
│ ├── gradle-4.8-bin
│ ├── gradle-4.9-all
│ └── gradle-4.9-bin
└── gradle.properties #全局gradle配置
复制
缓存清理
Gradle会周期性的自动清理缓存。默认情况下,Gradle daemon线程停止后就会执行清理过程。如果使用了--no-daemon
参数,清理过程在build完成后会显示在前台。
默认清理策略,每24小时执行一次
- caches下的特定版本目录会被检测是否被使用,如果没被使用,稳定版本30天被删除,快照版本7天被删除。
- 共享缓存也会被检测,如果没有特定版本的缓存使用对应的共享缓存就会被删除。
wrapper/dists/
取决于版本缓存和/cache/${version}目录是否一致,没用到的会被删除。
缓存执行频率
- DEFAULT:默认,24小时一次
- DISABLED:不执行。明确时间点清理时有用。
- ALWAYS:每次执行build操作完成后执行。影响性能。
配置策略,
${user_home}/.gradle/init.d/cache-settings.gradle
文件
beforeSettings { settings -> settings.caches { //默认30天 releasedWrappers.removeUnusedEntriesAfterDays = 45 //默认7天 snapshotWrappers.removeUnusedEntriesAfterDays = 10 //默认30天 downloadedResources.removeUnusedEntriesAfterDays = 45 //默认7天 createdResources.removeUnusedEntriesAfterDays = 10 //缓存清理执行策略 cleanup = Cleanup.DISABLED } }
复制
4.2 项目目录
Java项目的目录结构。
目录结构
├── .gradle #项目特定的gradle缓存
│ ├── 4.8 #特定版本缓存
│ ├── 4.9
│ └── ⋮
├── build #构建输出目录,编译的class文件和打包的jar包
├── gradle #项目的Gradle Wrapper
│ └── wrapper
├── gradle.properties #项目的gradle配置
├── gradlew #执行gradle Wrapper命令的脚本,sh
├── gradlew.bat #执行gradle Wrapper命令的脚本,bat
├── settings.gradle or settings.gradle.kts #多项目配置项目列表
├── subproject-one #子项目1
| └── build.gradle or build.gradle.kts #子项目1的gradle构建脚本,包含项目的配置信息,每个项目都有一个
├── subproject-two
| └── build.gradle or build.gradle.kts
└── ⋮
复制
4.3 配置文件定义
添加warrper镜像
编辑gradle-wrapper.properties文件
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
#使用镜像,不然zip包下载巨慢
distributionUrl=https://mirrors.cloud.tencent.com/gradle/gradle-7.4.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
复制
插件镜像配置
全局配置,在用户目录下的
.gradle/
文件夹下创建文件init.gradle
(这个文件默认是没有创建的,需手动创建)。如果配置了GRADLE_HOME,就放在GRADLE_HOME的init.d目录下面。
settingsEvaluated { settings -> settings.pluginManagement { repositories { maven { url "https://maven.aliyun.com/repository/gradle-plugin" } maven { url "https://maven.aliyun.com/repository/spring-plugin" } gradlePluginPortal() } } }
复制
项目配置,在项目的根目录的
settings.gradle
文件中第一行加入,注意这配置必须要放在开头
pluginManagement { //插件镜像 repositories { maven { url "https://maven.aliyun.com/repository/gradle-plugin" } maven { url "https://maven.aliyun.com/repository/spring-plugin" } gradlePluginPortal() } }
复制
依赖镜像配置
全局配置,用户根目录下/
.gradle/init.gradle
。如果配置了GRADLE_HOME,就放在GRADLE_HOME的init.d目录下面。
buildscript { repositories { maven { url 'https://maven.aliyun.com/repository/google' } maven { url 'https://maven.aliyun.com/repository/central' } maven { url 'https://maven.aliyun.com/repository/public' } } allprojects { repositories { maven { url 'https://maven.aliyun.com/repository/google' } maven { url 'https://maven.aliyun.com/repository/central' } maven { url 'https://maven.aliyun.com/repository/public' } } } }
复制
项目级配置,项目根路径/
build.gradle
buildscript { // 配置变量 ext { springBootVersion = '2.7.11' } // 插件仓库 repositories { maven { url "https://maven.aliyun.com/repository/gradle-plugin" } maven { url "https://maven.aliyun.com/repository/spring-plugin" } gradlePluginPortal() } // 应用插件需要的依赖包 dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") } } //依赖镜像 repositories { maven { url 'https://maven.aliyun.com/repository/central' } maven { url 'https://maven.aliyun.com/repository/public' } }
复制
Gradle控制台乱码
idea里面配置:help》edit custom vm options
#配置文件末尾加上这句后重启idea
-Dfile.encoding=UTF-8
复制
项目目录指定
rootProject.name = '' include('app') project(':app').projectDir = file('../app') project(':app').buildFileName = 'build.gradle'
复制
五、配置编写
5.1 生命周期
整个构建的生命周期围绕task展开,task和task之间构成一个有向无环图。用户可以编写groovy脚本自己任意构建自定义的构建流程。Java插件提供了默认的生命周期,如下图:

5.2 构建阶段
gradle的构建可以分为三个阶段:初始化、配置、执行。
初始化
初始化阶段主要是读取settings.gradle
配置,初始化需要构建的project实例。
- 读取settings.gradle配置,如果settings.gradle不存在,读取settings.gradle(.kts),如果也不存在就以单一项目进行build。
- 根据settings.gradle的配置信息决定构建哪部分项目,如果是多project就按多project。settings.gradle不存在按单一project处理。
- 为每个项目创建
Project
实例。每个项目都可以由子模块;除了root模块,所有模块都有一个父模块。
配置
在配置阶段,处理每个project实例的build.gradle
配置,并生成任务执行方案。
- 获取每个project的
build.gradle
配置 - 生成任务执行方案
配置阶段的两个生命周期回调:beforeProject和afterProject
gradle.beforeProject { project -> project.ext.set("hasTests", false) } gradle.afterProject { project -> if (project.ext.has("hasTests") && project.ext.get("hasTests") as Boolean) { def projectString = project.toString() println "Adding test task to $projectString" project.task('test') { doLast { println "Running tests for $projectString" } } } }
复制
执行
执行阶段,gradle根据配置阶段生成的任务执行方案来执行任务
-
按照任务执行方案顺序执行任务
执行任务就是一个build过程,包含:下载依赖、编译源码、读取输入、输出
执行阶段的两个生命周期回调:beforeTask和afterTask
gradle.taskGraph.beforeTask { Task task -> println "executing $task ..." } gradle.taskGraph.afterTask { Task task, TaskState state -> if (state.failure) { println "FAILED" } else { println "done" } }
复制
5.3 声明变量
本地变量
本地变量只能在声明的scope范围使用
def dest = 'dest' tasks.register('copy', Copy) { from 'source' into dest }
复制
提取属性
使用ext来提取属性,一次定义多次使用。作用域更广泛,子模块可以直接访问父模块定义的ext属性。
父模块的build.gradle中定义
ext { springVersion = "2.7.11" }
复制
子模块的build.gradle中使用
dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test:${springVersion}' implementation('org.springframework.boot:spring-boot-starter-web:${springVersion}') }
复制
5.4 task声明
先声明名称为hello的task
//注册hello的task tasks.register('hello') { doLast { println 'Hello world!' } }
复制
#执行hello任务
gradle -q hello
复制
任务依赖
后声明的intro依赖于hello任务,执行intro之前会先执行hello,如果依赖的任务不存在就会是延迟依赖,一样可以正常执行。
//intro的task依赖hello tasks.register('intro') { dependsOn tasks.hello doLast { println "I'm Gradle" } }
复制
任务延迟依赖
任务声明时依赖其他的任务,但是被依赖的任务此时还不存在就会延迟依赖。
tasks.register('intro') { dependsOn tasks.hello doLast { println "I'm Gradle" } } tasks.register('hello') { doLast { println 'Hello world!' } }
复制
复杂任务
注意,groovy中单引号代表字符串,双引号才能占位
动态占位声明4个任务,分别是task0,task1,task2,task3,
4.times {counter ->{ tasks.register("task$counter"){ doLast { println "this is task $counter" } } }} //将任务名称当成变量直接引用 tasks.named('task0'){dependsOn('task1','task2','task3')}
复制
#执行task0任务
gradle -q task0
复制
5.5 插件机制
Gradle中大量功能都是通过插件提供。向编译Java代码的功能就是提供一个Java插件。插件主要分为两类:二进制插件和脚本插件,二进制插件可以包含在脚本插件内部。一般的插件开始是更简单的脚本形式,插件发展后更才成为二进制插件以便于测试和传播。
plugins模块引入
新版使用plugins代码块声明插件,比旧版的apply声明更具约束性
插件引入主要分为两个阶段:
- relslove:解析插件的jar包版本并添加到脚本的classpath中,然后插件的功能就可以在构建脚本中使用。
- apply:项目执行
Plugin.apply(T)
方法,apply方法幂等。
二进制插件可以通过插件id引入,id是一个唯一标识。也可以通过name引入。Gradle的核心插件有简称,如JavaPlugin
的简称是java
;其他的一些插件就需要完整id,例如com.github.foo.bar
。
有简称的核心插件引入
plugins { id 'java' }
复制
无简称的社区插件,必须完整id,使用ext引用版本变量
plugins { id 'org.springframework.boot' version "${springBootVersion}" }
复制
可以在父模块的build.gradle中声明插件的版本apply等,然后在子模块中引用。
父模块
plugins { id 'java' id 'org.springframework.boot' version "${springBootVersion}" apply false }
复制
子模块
plugins { id 'java' id 'org.springframework.boot' } apply plugin: 'io.spring.dependency-management'
复制
apply引入
id引入,简称全名均可
apply plugin: 'java'
复制
type引入,被gradle默认引入模块包含
apply plugin: JavaPlugin
复制
buildscript代码块引入
buildscript { ext { springBootVersion = '2.7.11' } repositories { maven { url "https://maven.aliyun.com/repository/gradle-plugin" } maven { url "https://maven.aliyun.com/repository/spring-plugin" } gradlePluginPortal() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") } } apply plugin: 'org.springframework.boot'
复制
引入脚本插件
apply from: 'other.gradle'
复制
5.6 多包互相导入
多包项目中,一个包引入另外一个包。
app和app2,在app2项目中引入app项目,引入后app2中直接使用app的代码
dependencies { implementation project(':app') }
复制
六、依赖处理
gradle支持各种发布路径,不仅包含Maven支持的各类仓库地址发布,而且包含自定义任意路径发布。
6.1 发布jar包
引入插件
plugins { id 'maven-publish' }
复制
定义AGV
Artifact名称取project.name
属性,group和version也是project的对应属性。
group = 'org.xrj' version = '1.0.0'
复制
定义发布jar
这里定义了四种类型的Jar,分别是:
- app-1.0.0-plain.jar:class包,可以作为外部依赖使用。
- app-1.0.0-javadoc.jar:Java文档,内部是对应类的文档HTML文件。
- app-1.0.0-sources.jar:java包,内部是对应的Java源代码。
- app-1.0.0.jar:可执行Jar包,包含class包及其所有的依赖jar等。
java { withJavadocJar() //发布文档jar包,内部是javadoc的html文件,app-1.0.0-javadoc.jar withSourcesJar() //发布源码jar包,内部是源码java文件,app-1.0.0-sources.jar } publishing { publications { app(MavenPublication) { from components.java //发布归档jar包,内部是编译后class文件,app-1.0.0-plain.jar artifact bootJar //发布可执行boot包,内部包含所有依赖文件可java -jar运行,app-1.0.0.jar //版本映射策略,只发布bootJar时可以不用 versionMapping { usage('java-api') { fromResolutionOf('runtimeClasspath') } usage('java-runtime') { fromResolutionResult() } } } } }
复制
发布地址
发布地址和publications都在publishing代码块下。
每一个发布地址都会动态生成插件命令,这里定义了三类五种发布地址:
- mavenLocal:本地仓库,发布路径是
$USER_HOME/.m2/repository
。 - maven:自定义仓库,url可以指定本地目录和http地址进行发布,此处的name必须指定唯一值。
- 项目路径repo:指定项目build目录下的repos目录
- 自定义路径repo:手动指定Maven的repo目录
- 远端repo:直接指定私有库的https地址,并设置用户名密码验证信息
- mavenCentral:中央仓库,发布到Maven的中央仓库。
publishing { repositories { //发布到本地仓库,默认是路径$USER_HOME/.m2/repository mavenLocal() //项目路径repo maven { name 'localBuildRepo' def releasesRepoUrl = layout.buildDirectory.dir('repos/releases') def snapshotsRepoUrl = layout.buildDirectory.dir('repos/snapshots') url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl } //自定义路径repo maven { name 'myRepo' url = layout.projectDirectory.dir('D:\\jenviorment\\apache-maven-3.8.6\\repo') } //远端repo maven { name 'huawei-cloud' def releasesRepoUrl = 'https://devrepo.devcloud.cn-north-4.huaweicloud.com/artgalaxy/cn-north-4_28e3db20d17644c1b392216ca50a185d_maven_1_0/' def snapshotsRepoUrl = 'https://devrepo.devcloud.cn-north-4.huaweicloud.com/artgalaxy/cn-north-4_28e3db20d17644c1b392216ca50a185d_maven_2_0/' url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl credentials{ username 'xxx' password 'xxx' } } //发布到maven中央仓库,需要申请中央仓库的账号 mavenCentral() } }
复制
发布命令
6.2 依赖本地jar包
引入本地目录
repositories { //引入本地文件目录 flatDir(dirs: "lib") mavenCentral() }
复制
引入本地目录依赖
dependencies { // 依赖某个jar文件 implementation files('lib/xxx.jar') // 依赖libs目录下所有以.jar结尾的文件 implementation fileTree(dir: 'lib', includes: ['*.jar']) // 依赖libs目录下除了xxx.jar以外的所有以.jar结尾的文件 implementation fileTree(dir: 'lib', excludes: ['xxx.jar'], includes: ['*.jar']) }
复制
6.3 拆分打包SpringBoot
在对应项目底部增加下面的配置。然后执行bootJar进行打包即可。
拆分配置
// 将依赖包复制到lib目录 tasks.register('copyJar', Copy) { // 清除现有的lib目录 delete "$buildDir\\libs\\lib" //拷贝jar from configurations.runtimeClasspath into "$buildDir\\libs\\lib" from configurations.compileClasspath into "$buildDir\\libs\\lib" } // 拷贝配置文件 tasks.register('copyConfigFile', Copy) { // 清除现有的配置目录 delete "$buildDir\\libs\\config" from('src/main/resources') into 'build/libs/config' } //配置bootJar进行打包 bootJar { // 排除所有的jar excludes = ["*.jar"] // lib目录的清除和复制任务 dependsOn copyJar // 配置目录的清除和复制任务 dependsOn copyConfigFile // 指定依赖包的路径 manifest { attributes "Manifest-Version": 1.0, 'Class-Path': configurations.compileClasspath.files.collect { "lib/$it.name" }.join(' ') } }
复制
拆分结果
原本打包在一起的jar会被拆分成lib、配置、和代码jar三部分