前置准备
使用start.spring快速搭建一个Gradle的Web项目(https://start.spring.io/)
项目结构解析

├─build.gradle ①
├─gradlew ②
├─gradlew.bat ③
├─settings.gradle ④
├─gradle ⑤
│ └─wrapper
│ ├─ gradle-wrapper.jar
│ ├─ gradle-wrapper.properties
└─src ⑥
├─main
└─test
- 项目自动编译的时候要读取的配置文件。比如指定项目的依赖包等。
build.grade有两个,一个是全局的,一个是在模块里面。全局的build.grade主要设置的是声明仓库源,gradle的版本号说明等。 - linux下的gradle环境脚本,可以执行gradle指令,如:./gradlew build
- windows下的gradle环境,可以执行gradle指令
- 包含必要的一些设置,例如,任务或项目之间的依懒关系等,无论有多少个子模块,该文件只会有一个,且一定在根项目中;
- 包含wrapper文件夹及其2个子文件,作用是:可以自动安装gradle环境
- 程序源码
build.gradle基础结构
//插件声明
plugins {
id 'org.springframework.boot' version '2.7.4'
id 'io.spring.dependency-management' version '1.0.14.RELEASE'
id 'java'
}
//该项目的坐标信息
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
//仓库地址
repositories {
//这里推荐使用阿里云的镜像仓库,待会下载jar包的时候速度会很快
maven {
url 'https://maven.aliyun.com/repository/public/'
}
mavenCentral()
}
//第三方依赖jar
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
//单元测试声明
tasks.named('test') {
useJUnitPlatform()
}
打开cmd命令行,我们使用gradlew build来编译这个项目(默认会下载一个gradle-x.x-bin.zip),并使用java -jar build/libs/demo-0.0.1-SNAPSHOT.jar运行起来。
将项目导入到IDEA中,然后执行右侧build
再启动服务
到这里,我们前置准备工作已经就绪,接下来我们使用这个build.gradle来学习gradle的api
Gradle的Project
在gradle中,每一个build.gradle文件对应一个Project实例,我们在build.gradle中编写的内容,就相当于Project实例的属性或方法。
构建初始化期间,Gradle实例化的步骤如下:
- 给整个构建创建一个
Settings实例,一个Settings实例就是一个settings.gradle文件 - 针对
Settings实例的配置,按配置层次扫描并解析配置在settings.gradle中的project。(其中settings中最为重要的属性是include) - 针对每个
project对应的build.gradle进行初始,并创建Project实例(这里加载project的顺序是按照前面的配置层次进行,即广度扫描加载,这样可以保证父级模块加载完后,子模块才会被加载)
一个完整的project由以下几个对象组成(实际上只由属性和方法组成)
我们可以在build.gradle文件中任意使用gradle提供的属性或方法,如下:
//输出当前项目名称
println(project.name)
//一般在当前build.gradle中使用时,默认会省略project
println(name)
//输出project中名字为name的属性
println(project.property("name"))
//指定默认执行的task,即./gradlew不指定task时会执行该task
defaultTasks('yourTask')
属性
内置属性可以直接赋值,无需声明
group = 'com.it235' version = '1.0.0'自定义属性可以使用groovy语法,也可以与java语法结合
//groovy定义属性 def pname = "projectName:" + project.name //java类型接收 String pname = "projectName:" + project.name使用ext名命空间来扩展属性,定义后可以在project、task、subproject中读取和更新
ext.prop1 = "it235" ext.prop2 = "君哥聊编程"使用案例
//REPOSITORY_HOME 和 REPOSITORY_URL apply plugin: 'maven' ext { REPOSITORY_HOME = 'http://maven.aliyun.com' REPOSITORY_URL = REPOSITORY_HOME + "/nexus/content/groups/public" } repositories { // 使用 mavenCentral()时,将远程的仓库替换为自己搭建的仓库 maven { url REPOSITORY_URL } } uploadArchives { repositories { mavenDeployer { snapshotRepository(url: REPOSITORY_HOME + "/nexus/content/repositories/snapshots/") { authentication(userName: 'xxx', password: 'xxx') } repository(url: REPOSITORY_HOME + "/nexus/content/repositories/releases/") { authentication(userName: 'xxx', password: 'xxx') } } } }属性作用域
读写属性时,Project 会按照下面范围的顺序进行查找的,在某个范围找到属性后就会返回该属性。如果没有找到,会抛出异常。
Project对象自身。这个范围里的属性包含Project实现类中定义有 getters 和 setters 方法的所有属性。比如:project.getName()方法就对应了name属性。至于这些属性的可读写性取决于它们是否定义 getters 或者 setters 方法。- Project 的ext属性 ( extra ) 。每个 Project 都会维护一个额外属性的映射,它可以包含任意的
名称 -> 值对。定义后,此作用域的属性是可读写的。比如:project.ext.prop1 = 'it235'。 - 通过插件被添加到 Project 中的扩展属性 (
extensions) 。每个扩展名都可以作为只读属性使用,其名称与扩展名相同。比如:project.android.compileSdkVersion。 - 通过插件添加到 Project 中的约定属性 ( convention ) 。插件可以通过 Project 的 Convention 对象向 Project 中添加属性和方法。此范围的属性的可读可写性取决于约束对象。
- Project 中 Tasks 。可以使用 Task 的名称作为属性名称来访问task。此范围的属性是只读的。
- ext的属性和约定属性从项目的父级继承,递归到根项目。此范围的属性是只读的。
常用的project属性
allprojects:包含此项目及其子项目的集合。buildDir:当前项目的编译目录(自动生成)默认值 porjectDir/builddefaultTasks:当前项目的默认任务的名字集,当前构建没有提供任务名时会执行这些默认任务group:当前项目的组名logger:当前项目的日志器,可以用来在 build 文件中写日志name:此项目的名称parent:此项目的父项目path:这个项目的绝对路径project:当前project对象实例rootDir:本项目的根目录。根目录是根项目的项目目录rootProject:当前项目层次结构中的根projectsubprojects:当前项目的子项目的集合tasks:本项目的task集合。version:此项目的版本
方法
方法作用域
- Project 对象自身
- build.gradle 脚本文件
- 通过插件添加到 Project 中的扩展 ( extensions ) 。每个扩展都可以当做参数是闭包或 Action 的方法。
- 插件添加到项目中的约定方法 ( convention ) 。插件可以通过项目的 Convention 对象向项目中添加属性和方法。
- 项目中的 Tasks 。每个 Task 都会添加一个方法,方法名是任务名,参数是单个闭包或者 Action 。该方法使用提供的闭包为相关任务调用
Task.configure( groovy.lang.Closure )方法。
常用的Project方法
方法 描述 afterEvaluate可以添加一个闭包,它会在项目完成评估后立即执行。当执行属于该项目的构建文件时,会通知此类监听器。 allprojects配置当前项目以及它的每个子项目 apply应用零个或多个插件或脚本。 beforeEvaluate添加一个闭包,它会在项目开始评估前立即执行 configure通过闭包配置对象集合。 copy复制指定的文件 defaultTasks设置此项目的默认任务的名称。当开始构建时没有提供任务名称时使用这些。 delete删除文件和目录 exec执行外部命令 file解析相对于该项目的项目目录的文件路径 findProject按路径定位项目。如果路径是相对的,则相对于该项目进行解释。 findProperty找特定属性,如果未找到,则返回给定属性的值或 null getAllTasks返回此项目中包含的任务的地图 hasProperty确定此项目是否具有给定的属性 javaexec执行 Java 主类 javaexec执行外部 Java 进程。 mkdir创建一个目录并返回一个指向它的文件。 property返回给定属性的值。此方法定位属性如下: setProperty设置此项目的属性。此方法在以下位置搜索具有给定名称的属性,并将该属性设置在它找到该属性的第一个位置。 subprojects配置本项目的子项目 task创建 Task具有给定名称的 a 并将其添加到此项目中uri将文件路径解析为 URI,相对于该项目的项目目录
常用方法示例
buildscript{}:配置当前gradle脚本自身需要使用的构建信息或依赖
假设要执行一项指令
./gradlew buildImage,构建docker镜像,而Gradle官方自身没有,则需要依赖到maven库下载或需要调用第三方插件,虽然这里是调用的task,但是task背后所依赖的插件是需要提前定义在buildscript中的,我们需要在buildscript{}中指定docker的依赖即可。apply plugin: 'idea' apply plugin: 'java' apply plugin: "maven" apply plugin: "war" apply plugin: "com.bmuschko.docker-remote-api" apply plugin: "org.springframework.boot" buildscript { repositories { mavenLocal() maven { url "https://maven.aliyun.com/repository/public" } mavenCentral() } dependencies { classpath "com.bmuschko:gradle-docker-plugin:3.3.4" classpath "org.springframework.boot:spring-boot-gradle-plugin:2.6.5" } }configurations{}:配置使用声明的依赖项用于特定目的(听君一席话,如听一席话)
我们看个案例,下面的
implementation和testRuntime就是Gradle帮我们提供的configuration,configurations{}记录着项目中各个分组(implementation,runtime)的依赖信息。dependencies { implementation "org.springframework.boot:spring-boot-starter-web" testRuntime "junit:junit:4.13" }简单一句话概括configurations{}的作用:将本项目需要的相关依赖资源进行分组,A组你可以提供在运行时使用,B组你提供在测试运行时使用,C组编译时使用,D组xxxx?
因为
configuration支持继承进行扩展,子配置会集成所有父配置的依赖,testImplementation extends implementation,如下图
那implementation、testImplementation是谁帮我们提供的呢?当然是java的插件啦,插件声明如下(后面插件环节会单独讲解):apply plugin: 'java' //或者如下定义 plugins{ id 'java-library' }
下图是java插件提供的测试相关的configuration
关于扩展功能,还允许我们开发者自定义configurations { //声明一个具备加载测试依赖的configuration smokeTest.extendsFrom testImplementation //声明一个具备类编译和运行的configuration compileClasspath.extendsFrom(someConfiguration) runtimeClasspath.extendsFrom(someConfiguration) //全局排除log4j依赖 implementation.exclude group:'org.apache.logging.log4j' implementation.exclude module:'spring-boot-starter-log4j2' //声明一个具备类运行的configuration developmentOnly runtimeClasspath { extendsFrom developmentOnly } } dependencies { testImplementation 'junit:junit:4.13' //读取根目录下的lib目录 smokeTest fileTree('lib') smokeTest 'org.apache.httpcomponents:httpclient:4.5.5' //使用someConfiguration将lib模块添加到本项目依赖 someConfiguration project(":lib") developmentOnly("org.springframework.boot:spring-boot-devtools") } //将文件拷贝到另外一个目录 afterEvaluate{ //configurations属性可以在任意一个task中读取 println configurations.smokeTest.files.first() println configurations.smokeTest.asPath def libPath = projectDir.absolutePath + "/src/main/lib2" copy { from configurations.smokeTest.files.first() into libPath } }
repositories{}:仓库配置
通过 repositories{} 可以配置maven,ivy,local仓库。这样子,在dependencies{}声明的依赖就可以通过repositories{}中指定的仓库查询到具体的JAR资源。
repositories { mavenLocal() mavenCentral() maven { // Name is optional. If not set url property is used name = 'Main Maven repository' url = 'https://maven.aliyun.com/repository/central' } //有权限控制的仓库 maven() { credentials { username = 'username' password = 'password' } url = 'https://maven.aliyun.com/repository/central' } //本地仓库 repositories { flatDir(dir: '../lib', name: 'libs directory') flatDir { dirs '../project-files', '/volumes/shared-libs' name = 'All dependency directories' } } }dependencies{}
在gradle中dependencies{}是一等公民,它描述了configurations{}中分组依赖的第三方资源。我们可以把依赖简单的分成两大类:- gradle依赖:主要是gradle运行的时候,需要加载一些插件,如android等,此时需要配置它。
- 项目编译/运行依赖:编译一个项目,通常需要依赖其他项目或者JAR。
依赖的常规写法(与maven一致)

//配置依赖 dependencies { //compile已过时,推荐使用implementation //按照maven名称加载jar implementation 'com.google.guava:guava:11.0.2' //排除部分依赖 implementation('org.slf4j:slf4j-simple:1.6.4') { exclude 'org.slf4j:slf4j-api' } //测试模块 testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.2' //依赖其他子项目 implementation project(':projectA') //加载目录下所有的jar implementation fileTree(dir: 'libs', include: ['*.jar']) //多个文件 implementation ('com.google.guava:guava:11.0.2'){ //在版本冲突的情况下优先使用该版本 isForce = true //禁用依赖传递 transitive = false } } //使用dependencis任务可以查看当前依赖树,*表示被忽略的
allprojects{}:配置此项目及其每个子项目所需要的依赖。一般在多模块项目场景下我们会把公共的部分配置在根项目的
allprojects中。apply plugin: 'java' apply plugin: "maven" apply plugin: "war" apply plugin: "com.bmuschko.docker-remote-api" apply plugin: "org.springframework.boot" buildscript { repositories { mavenLocal() maven { url "https://maven.aliyun.com/repository/public" } mavenCentral() } dependencies { classpath "com.bmuschko:gradle-docker-plugin:3.3.4" classpath "org.springframework.boot:spring-boot-gradle-plugin:2.6.5" } } allprojects { apply plugin: "idea" repositories { mavenLocal() maven {url "https://maven.aliyun.com/repository/public"} mavenCentral() } // 依赖管理器 dependencyManagement { imports { mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Hoxton.SR12' mavenBom 'com.alibaba.cloud:spring-cloud-alibaba-dependencies:2.2.6.RELEASE' } } // ...各种配置... //依赖 dependencies { implementation("org.springframework.boot:spring-boot-starter") { exclude module: "tomcat-embed-el" } implementation("org.springframework.boot:spring-boot-starter-web") api 'com.squareup.retrofit2:retrofit:2.4.0' //仅编译 compileOnly 'org.projectlombok:lombok:1.18.16' annotationProcessor 'org.projectlombok:lombok:1.18.16' //仅测试编译 testCompileOnly 'org.projectlombok:lombok:1.18.16' testAnnotationProcessor 'org.projectlombok:lombok:1.18.16' // ...依赖超级多的 jar 包,包括公司的包和三方包.... implementation "xxx.xxx.xxx" ... } }- subprojects{}:子模块配置
sourceSets{}:配置源码信息
maven和gradle默认约定的项目目录一样,如下:src/main/ src/main/java/ src/main/resources/ src/test/ src/test/java/ src/test/jresources/但是Gradle提供了修改的默认目录的功能,官方叫Changing the project layout,对于Java来说基本上不会调整项目结构,但也会存在特殊情况,如:过滤掉某个main目录下的文件夹,将一个外部文件夹纳入源码资源管理中。这个时候
sourceSets登场了,sourceSets的功能攘括了源文件及位置定义、相关依赖管理、定义编译输出的位置这3块。(其中main和test默认被官方定义在的sourceSets,无需手动指定)sourceSets有一些属性,比如名字、源文件位置、资源位置、classpath等等,这些属性有些是只读的,有些是可写的,并且可以被当作变量来引用。有可写的属性那么就提供了自定义的功能了,比如,你可以改变一个SourceSet源代码的位置,像这样下面这样,你就改变了main这个SourceSets的源代码目录和资源目录。sourceSets { main { java { srcDirs = ['src/java'] } resources { srcDirs = ['src/resources'] } } }这样,gradle就只在src/java下搜源代码,而不是在src/main/java下。如果你只是想添加额外的目录,而不想覆盖原来的目录,则像下面这样:
sourceSets { main { java { srcDir 'thirdParty/src/main/java' } } }此时,gradle就会同时在
src/main/java和thirdParty/src/main/java两个目录下都进行源代码搜索。注意:你可要注意srcDir和srcDirs的区别
同时我们还可以排除classpath下面的某个目录,你可以这样做,执行build之后,你在jar或war包的classes中不会见到排除掉的目录
sourceSets { main{ java { exclude 'com/it235/first/ct/**' } resources { exclude 'cert/**' } } } 我的目录结构 first-gradle src main java com.xxx resources cert test.cert application.properties指定源码输出目录
sourceSets { main { //指定main的源码输出位置 output.resourcesDir = output.classesDir = 'WebContent/WEB-INF/classes/' //源码目录 java.srcDir('src') //资源文件目录 resources.srcDir('src') } }什么情况下我们会添加一个新的
SourceSets
原始的src/test目录我们一般存放单元测试,但是对于集成测试部分不应存放在src/test目录下,因为这些测试有它们自己的dependencies, classpaths, 以及其他的资源,所以这些测试应该是单独分开的,而不应该和单元测试混为一谈。这样会将2个测试工作进行混淆,也是架构师们不希望看到的局面,所以我们需要重新定义一个用于集成测试的代码环境。但是,集成测试和验收测试也是这个项目的一部分,因此我们也不能为集成测试或验收测试单独建立一个项目。它们也不是子项目,所以用subproject也不合适。此时,我们就可以新建一个集成测试或者验收测试的SourceSets,把相关的测试资源管理起来。
在src目录下新建一个
intTest作为集成测试的环境,与src/main、src/test保持一致,格式如下first-gradle src intTest -- 存放集成测试的源码 java resources main -- 存放应用源码 java resources test -- 存放单元测试源码 java resources- 为他们创建一个新的
sourceSets - 将所需的依赖项添加到该
sourcesSets配置中 为该源集配置编译和运行时类路径
configurations { //intTestImplementation 扩展至implementation,说明它具备生产源码的各项依赖 intTestImplementation.extendsFrom implementation intTestRuntimeOnly.extendsFrom runtimeOnly //如果集成测试需要单元测试的依赖可以采用如下做法 //intTestImplementation.extendsFrom testImplementation } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation 'org.springframework.boot:spring-boot-starter-test' intTestImplementation 'org.springframework.boot:spring-boot-starter-test' } sourceSets { //这里因为我们的intTest目录与闭包命名一致,所以不使用srcDir添加也能自动识别 intTest { //将正式项目的源码编译和运行时路径给到intTest,记住是+=,而不是覆盖 compileClasspath += sourceSets.main.output runtimeClasspath += sourceSets.main.output } }创建一个任务来运行集成测试
//任务类型是Test task integrationTest(type: Test) { description = 'Runs integration tests.' //这里指定后,该任务将会在IDE右侧的verification分组里面 group = 'verification' //配置已编译的测试类的位置 testClassesDirs = sourceSets.intTest.output.classesDirs //运行我们的集成测试时使用的类路径。 classpath = sourceSets.intTest.runtimeClasspath //声明在test单元测试完成后自动执行集成测试(非必须) shouldRunAfter test //mustRunAfter表示必须在test任务执行完成之后执行 } //定义一个检查任务,让集成测试在检查任务之前运行,如果集成任务构建失败,检查任务也会失败(非必须) check.dependsOn integrationTest最后注意定义test单元测试引擎
tasks.withType(Test) { useJUnitPlatform() } //以下写法不生效,暂未找到原因 test { useJUnitPlatform() }完整的配置如下
buildscript { repositories { mavenLocal() maven { url "https://maven.aliyun.com/repository/public" } mavenCentral() } dependencies { classpath "org.springframework.boot:spring-boot-gradle-plugin:2.6.5" } } plugins { id 'org.springframework.boot' version '2.6.4' id 'io.spring.dependency-management' version '1.0.11.RELEASE' id 'java' } group = 'com.it235' version = '1.0.0' repositories { maven { url 'https://maven.aliyun.com/repository/central' } } configurations { intTestImplementation.extendsFrom implementation intTestRuntimeOnly.extendsFrom runtimeOnly } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation 'org.springframework.boot:spring-boot-starter-test' intTestImplementation 'org.springframework.boot:spring-boot-starter-test' } sourceSets { intTest { compileClasspath += sourceSets.test.runtimeClasspath runtimeClasspath += sourceSets.test.runtimeClasspath } } tasks.withType(Test) { useJUnitPlatform() } task integrationTest(type: Test) { //tasks.register('integrationTest', Test) { description = 'Runs integration tests.' group = 'verification' testClassesDirs = sourceSets.intTest.output.classesDirs classpath = sourceSets.intTest.runtimeClasspath shouldRunAfter test } check.dependsOn integrationTest
artifacts{}:配置需要交付的产品组件信息
artifacts的意思是产品、产物,可以理解为一个可以交付的jar或者war。artifacts主要的用途:定义好产物构建规则,然后结合archives或publishing发布到私服或中央仓库
传统的构建artifacts一般是结合IDE工具进行的,这里我新建一个普通的Gradle项目:
second-gradle,新建也给Java类package com.it235.second; public class SecondApplication { public static void main(String[] args) { System.out.println("start SecondApplication"); } }然后采用IDEA的传统方式进行打包,流程如下:
File->Project Structrue->Project Settings->Artifacts,选中From modules with dependencies





15年前我们可能经常这么干,但是以上的操作不够灵活,比如多模块的情况下我们有特殊处理,版本也没有办法管理,随着Maven和Gradle的强大,我们采用了项目管理工具来构建,也就是接下来我要说的。生成artifact产品主要有以下3种途径
使用已经定义好的Task
task myJar(type: Jar) artifacts { archives myJar } 或 configurations { xxx } task myJar2(type: Jar) artifacts { xxx myJar2 } //仔细一看,好像干了什么,又好像什么都没干,使用示例如下 //实例1,定义jar包构成 task myJar2(type: Jar){ from compileJava.outputs , "src/main/resources" manifest { attributes( "Manifest-Version": 1.0, 'Main-Class': 'com.it235.second.SecondApplication', ) } } //实例2,生成sql.zip task dbChangelogZip(type: Zip) { from 'src/main/resource4db' baseName 'order-db-changelog' //输出的目标目录 destinationDir(file('build/libs')) } //实例3,生成source源码 task mySourceJar(type: Jar){ archiveClassifier = 'sources' from sourceSets.main.allSource } artifacts { archives myJar2 archives mySourceJar archives dbChangelogZip }使用一个file文件(暂时没有想到使用场景)
def someFile = file('build/somefile.txt') artifacts { archives someFile } artifacts { archives file: file('build/libs/second-gradle-1.0-SNAPSHOT.jar'), builtBy: war }
publishing{}:deploy当前项目到仓库
plugins { ... id 'maven-publish' ... } publishing { publications { //打springboot-jar包 uesJar(MavenPublication) { artifact bootJar from components.java } //可单独执行publishDbchangelogPublicationToUesRepository 上传dbchangelog压缩包 dbchangelog(MavenPublication) { artifactId 'ues-db-changelog' artifact dbChangelogZip } } repositories { maven { //忽略https allowInsecureProtocol true name = 'ues' def releasesRepoUrl = "http://192.168.35.xx:8081/nexus/content/repositories/releases/" def snapshotsRepoUrl = "http://192.168.35.xx:8081/nexus/content/repositories/snapshots/" url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl credentials { username 'admin' password 'admin123' } } } }
总结
本篇到这里就结束了,通过对Project的学习和了解,我们知道了build.gradle的构成,已经每个内置api的基本使用,后续文章会跟着实战来详细介绍这些api的使用,我们下一篇见。