起因
之前拉取CAS
源码和spring-framework
源码时候两者都是使用Gradle
构建的项目,由于从来没接触过,很陌生拉的源码完全跑不起来,不过从spring
工程可见Gradle
构建项目将会主流,所以有必要学习下。
Gradle是一种基于Groovy和Kotlin的开源自动化构建工具,它结合了Apache Maven的依赖管理和Apache Ant的灵活性和控制。Gradle主要用于自动化构建、测试、打包、部署等软件开发过程中的任务,并支持多种编程语言和平台,如Java、Kotlin、Groovy、Scala、C++等。
官方文档:Getting Started
Gradle其本质是对maven
的一种平替,都是项目构建工具~
1 Gradle 核心概念
这部分都是基础概念,过个眼熟并不深入,不过这些概念有个大概理解很重要!
1.1 Gradle 基础
gradle通过build脚本自动化构建、测试、部署软件。
1.1.1 核心概念
Projects
就是我们自己写的项目,对应上图左边部分,一个项目包括源码 + build.gradle,这个build.gradle就是maven中的pom.xml的概念,在这里定义依赖管理,插件管理等一些列的东西。Gradle
说到的子项目概念对应Maven
工程的模块概念,一个项目(文件夹目录)对应一个构建脚本.gradle
结尾的脚本文件,每个项目都会创建一个Project
对象表示,没错是创建对象,Java
中的概念,因为Gradle
本身是基于Java
写的,而编译,测试,打包的目标不就是项目这个文件夹目录嘛
Build Scripts
Build Scripts(构建脚本)这里的脚本对应maven
其实就是pom.xml
文件,只不过因为他是用groovy
或者kotlin
语言写的(所以说灵活),因此都叫build scripts
(构建脚本),在这里定义依赖还有插件。
这种以.gradle
结尾的文件就是pom.xml
的性质,但需要注意,他是脚本语言groovy
或者kotlin
因此可以写一些函数方法之类的,见到也不要陌生。
Dependency Management
Dependency Management(依赖管理)核心部分了,项目主要需要负责的就是依赖管理问题,这个词在Maven
中一样的。
Tasks
Tasks(任务)是最基本的工作单位,比如编译代码,就是一个任务;执行测试,就是另一个任务。理解上类似Maven
的生命周期,mvn clean、mvn compile、mvn package
等,一个指令对应这里的一个任务Task
;但是他这个在这里更灵活,在项目构建的时候会编排一个任务链,然后依次执行,并且我们自己自定义任务,比如编译之后执行打印语句println('Hello World!')
也是可以的。
因为任务是为了构建项目,所以任务Task
这个类的实例是服务于Project
的,这也是为什么只有创建Project
这个对象实例后,才有任务实例对象,了解这点有助于理解build.gradle
构建脚本的编写!
命令gradle clean、gradle build
这个clean
和build
就是任务Task
!
Plugins
用于扩展gradle
功能的,跟maven
中定义插件一个意思,比如构建一个编译插件。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
1.1.2 项目结构
gradle
构建的项目目录结构大概为下面这种。
其中gradle
这整个目录是由gradle wrapper
命令创建的,非必需的,但是几乎所有项目都会用gradlew
这个包装器构建项目,而不是gradle
本身。 因此新建项目默认也是包括这个目录的。
具体的实例参考spring-framework
源码目录👇
- 其中根项目(Root Project)就是spring-framework
- 每一个子模块,比如spring-web,spring-context就是一个子项目(sub-project),他们也有自己的构建文件
.gradle
,类似每个项目都有一个pom.xml
文件,每个项目和构建脚本(.gradle
)结尾的文件一对一关系。 - gradlew这个目录定义了怎么下载gradle,可以理解为一个工具类,这样做的好处是你不需要像maven一样还需要单独下载一个maven软件包。
注意gradle
才是软件本身,gradlew
是一个工具包,用来辅助下载gradle
的,这就是为什么从github
拉取一个项目我不需要安装gradle
的原因,因为项目本身就存在gradlew
工具包了,在项目构建的时候他会先根据gradle-wrapper.properties
这个配置文件指定的网址(distributionUrl
)下载gradle
,下载的位置就是用户目录下的./gradle/wrapper/dists
,这么做的好处是项目指定了gradle
版本信息,因此不存在版本不兼容的问题。
这也是为什么网上说如果你下载gradle
失败可以通过镜像网站下载一个gradle
然后手动放在./gradle
目录下的原因。
不过国内最好的做法还是修改国内镜像下载最好,如上图👆
镜像网站:
- https://mirrors.cloud.tencent.com/gradle/
- https://mirrors.aliyun.com/gradle/
- https://mirrors.aliyun.com/macports/distfiles/gradle/
1.1.3 执行gradle
像IDEA默认是内嵌了gradle
的,因此我们可以直接使用
也可以通过命令行方式使用
gradle build
1.2 Gradle Wrapper 基础
大多数情况下,都是推荐使用gradle wrapper这个包装器构建项目而不是gradle本身。
通过gradlew
构建项目会先根据指定的版本下载gradle
到本地,然后再构建项目。
通过gradlew
构建项目存在两个脚本文件,对于Mac/Linux
可用的gradlew
,对于Windows
可用的gradlew.bat
。
这整个目录就是所谓的gradle wrapper
(gradle的包装器),这个目录本身不执行,执行命令的是gradlew
或者gradlew.bat
这个脚本文件(在项目根目录下),这个脚本命令几乎跟gradle
无任何区别,这个目录通过IDEA
创建项目会自动生成,或者使用命令gradle wrapper
也会生成。
为什么需要gradle wrapper?
- 指定
gradle
版本可以让一个项目标准化 - 对于不同的用户针对同一个项目使用的都是同一个版本的gradle
- 保证了执行环境的一致性
1.2.1 使用
既然是脚本文件,那么直接使用就好了(注意在当前项目目录下执行,毕竟脚本文件没有配置环境变量)。
Mac/Linux
用户
./gradlew build
Windows
用户
.\gradlew.bat build
命令使用几乎和gradle
完全一致!
1.2.2 理解
这是gradle-wrapper
这个包装器的目录结构,项目中gradle文件夹 + gradlew + gradlew.bat
= gradle-wrapper
这个包装器。
gradle-wrapper.jar
就是这个东西负责下载和安装gradle
的gradle-wrapper.properties
定义了下载gradle
的网址和安装目录gradlew
为Mac/Linux
用户的脚本文件gradlew.bat
为Windows
用户的脚本文件
配置文件
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
# 官网地址,外网很卡,下载很慢,一个`gradle`压缩包大概 130MB 大小
#distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
# 换成镜像下载
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
# 这是gradle-8.5-bin.zip这个文件存放的磁盘位置,具体在用户目录下./gradle/
zipStorePath=wrapper/dists
每次执行命令之前,都会先去查找这个配置文件指定的版本gradle
,如果有就直接使用,如果没有则根据URL
下载一个新的,放在目录$GRADLE_USER_HOME/.gradle/wrapper/dists
目录下,其中GRADLE_USER_HOME
默认就是当前用户的家目录。其实gradlew
这个命令执行的本质还是由gradle
执行,但因为每次都会优先查找指定版本的gradle
因此就可以避免不兼容的问题,比如我本地安装的是gradle-8.9
,但是项目如上配置的是gradle-8.5
,如果我是用gradle build
命令那么就是用本地8.9
版本构建的项目,如果我使用./gradlew build
那么就是使用8.5
版本构建的项目。
更新版本
如果gradlew
需要指定新的版本,使用下面的命令。
./gradlew --version
./gradlew wrapper --gradle-version 8.10
1.3 settings.gradle 文件基础
这个文件的主要作用是添加子项目,在maven
中就是下面这样
<name>java-web</name>
<modules>
<module>spring-demo1</module>
<module>spring-demo2</module>
</modules>
settings.gradle
rootProject.name = 'java-web'
include('spring-demo1')
include('spring-demo2')
1.4 build.gradle 文件基础
build.gradle
也叫构建脚本,这玩意里面的写法是Groovy
或者Kotlin
脚本语言。这个文件等价于pom.xml
功能,构建脚本的配置,依赖管理,插件添加都是在这里定义的。
这个文件主要包含两个部分:Gradle
本身需要的依赖和插件和项目所需要的依赖。
最简单的例子
// 添加插件,这个插件就叫 application
plugins {
id 'application'
}
// 这个插件的功能语法,他可以指定启动的类
application {
mainClass = 'com.example.Main'
}
1.5 任务Tasks基础
gradle里边的任务概念,就是把构建项目的过程进行任务的拆分,比如编译字节码文件,打包成JAR
包生成JavaDoc
等,分别对应不同的task任务。
如果我们运行./gradlew build
这个build
就是一个Task
。
通过这个命令可以知道一个项目所有的Tasks
./gradlew tasks
输出
这些绿色的字就是Task
,可以看到包括run
,build
,clean
这种,其实本质就是命令了。
好比mvn install
命令,这些任务Task
也是存在依赖关系的,像我们Java
代码,都需要先编译,那么我们构建之前,就会自动执行编译先,这就是Task denpendency
任务依赖的概念。
1.6 插件 Plugins基础
就像上面说的Task
,插件主要目的就是扩展这种功能让gradle
可以执行更多的task
选项和配置。
1.6.1 插件来源
插件主要包括三类:
- 核心插件;这主要是
gradle
官方的。 - 社区插件;比如
spring-boot
为了支持gradle
构建项目由spring
提供的插件,这种第三方插件就是社区插件。 - 本地插件;就是根据
gradle
的API自己写的插件。
1.6.2 使用插件
在构建脚本(build.gradle
(注意前缀build
可任意))文件中使用,其语法是groovy
或者kotlin
。
1.6.2.1 核心插件
java
插件
// 基本格式
plugins {
id «plugin id» version «plugin version»
}
// 具体例子 ==> 由于 `java` 属于核心插件,不同版本的`gradle`都有默认的版本保持兼容性,这里直接使用 id 'java'
plugins {
id 'java'
}
1.6.2.2 社区插件
spring-boot
plugins {
id 'org.springframework.boot' version '3.3.2'
}
这个东西是spring-boot
的,在官方文档给出了语法👇
1.6.2.3 本地插件
属于插件开发内容了,略过。
2 Gradle 教程(进阶实操)
2.1 初始化项目
使用项目大概就两种情况,创建的新项目和拉取别人的项目
2.1.1 创建新项目
方式一:IDEA直接构建(推荐)
文件 > 新建 >项目
新建项目会自动构建项目,但是由于包装器默认的下载gradle
的链接访问很慢,因此需要配置镜像!
将distributionUrl
部分替换下面的内容(注意版本号)
distributionUrl=https://mirrors.cloud.tencent.com/gradle/gradle-8.5-bin.zip
其他镜像链接
https://mirrors.aliyun.com/macports/distfiles/gradle/
方式二:gradle
命令行
Step 0:准备
既然是命令行自然要先安装gradle
了,我是Mac
直接通过homebrew install gradle
命令安装的。其他的安装可以参考安装教程,主要是为了存在环境变量,然后可以直接使用gradle
命令。
Step 1:初始化项目
创建项目目录
mkdir gradle-demo && cd gradle-demo
初始化命令
gradle init --type java-application --dsl groovy
除了JDK版本选择其他一路默认即可
初始化好的项目结构如下:
2.1.2 导入项目
除了自己创建一个新项目,还有一种情况就是拉取别人的项目,比如查看spring
源码,以此为例。
git clone
添加项目
git clone https://github.com/spring-projects/spring-framework.git
IDEA
直接打开项目,找到gradle
目录,配置镜像(注意不要修改版本号)
2.1.2 构建项目
IDEA内嵌Gradle
和内嵌Maven
是一个意思,默认导入的项目在右侧都会存在如下的按钮用于构建项目
但是这些按钮在还没有构建成功之前是没有的! 通常我们能看到的只有一个标记用于提示点击刷新(构建项目)
对于新项目,直接点击刷新执行构建即可,对于上面比如导入的spring-framework
源码可能就会遇到各种奇奇怪怪的问题,这本质是很多依赖或者所需的插件软件等下载不下来的问题。
在我们点击这个按钮的时候,就会为我们的项目解析依赖,解析依赖默认中央仓库下载很慢,因此我们需要配置国内镜像下载源。
这里就要区别于Maven
了,我们已经知道Gradle
工程中每个项目都存在一个build.gradle
对应于maven
中的pom.xml
文件,跟maven
比较而言,除了配置依赖项这个构建脚本build.gradle
还需要配置maven
仓库,临时性的配置如下:
build.gradle
repositories {
mavenLocal()
maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
maven { url 'https://maven.aliyun.com/repository/central' }
mavenCentral()
}
但是一定要注意,这个配置仅对当前一个项目有效,如果添加一个子模块,他是不生效的,原因是一个项目对应一个build.gradle
构建脚本。
也可以在build.gradle
文件中,配置当前项目全局性的仓库地址,allprojects{}
可以为当前项目包括子项目都配置仓库地址。
但是每次创建新项目我们都得这么配置一遍,我们并不能像maven
那样一次性配置settting.xml
文件后就一劳永逸,这是因为gradle
没有固定的版本软件使用(通过wrapper包装器一个项目指定一个版本的gradle),要实现这种配置全局性的依赖下载源的办法也不是没有,而这就需要你理解gradle
的生命周期了。
2.2 生命周期
生命周期指的是gradle
构建项目的流程,主要包括三个阶段:
- 初始化:加载初始化脚本,执行
setting.gradle
脚本,根据此脚本查找所有项目,为每个项目创建一个Project
的实例对象,便于下一步配置。 - 配置:执行每个项目的
buidl.gradle
构建脚本,这个构建脚本就包括了每个项目所需要的依赖,而这个过程我们称之为为当前项目Project
配置,并生成一个执行任务图(TaskExecutionGraph) - 执行:按照执行任务图,开始执行每个Task。
其实在初始化阶段还包括一点:查找初始化脚本(官网没有特别说明)!
官网图示
我们一定要清楚一点,Gradle
是基于Java
写的,他不像pom.xml
那样根据约束进行纯文本的配置,因此里边存在很多Object
对象的概念,项目被抽象为类Project
,而要操作这个类,自然就需要实例化对象。
2.2.1 初始化
在执行./gradlew build
的时候,首先会加载初始化脚本,即.gradle
结尾的文件,查找规则如下:
- 命令行参数指定初始化脚本,
./gradlew build -I init.gradle
,其中init.gradle
就是你写的初始化脚本,-I
等价--init-script
- 添加一个文件
init.gradle
在$GRADLE_USER_HOME/
目录下 - 添加一个文件
init.gradle
在$GRADLE_USER_HOME/init.d/
目录下 - 添加一个文件
init.gradle
在$GRADLE_HOME/init.d/
目录下
**$GRADLE_USER_HOME
默认就是(~/.gradle或者C:\Users\<USERNAME>\.gradle)
目录,也就是当前用户所在目录了,$GRADLE_HOME
是你安装时候配置的环境变量,没有就是没配。
因此我们进行全局性的依赖下载源就可以在这里配置。 具体的操作步骤参考 3.1
配置下载源
加载完启动脚本后,会在当前项目下查找settings.gradle
文件,从语义理解他就是一个配置文件,不过也是脚本文件,因此叫配置脚本,他的作用是根据此文件的配置找到需要实例化的Project
对象,如果没有添加,则不会执行。
比如项目存在三个子项目,但是在settings.gradle
配置时候没有设置第三个项目。
通过setting.gradle
文件,gradle
就可以知道下一步需要实例化的Project
有root
根项目,sub-project1
子项目,sub-project2
子项目。
然后再依次进行实例化,这一步完成,我们就有了Project
这个类的实例化对象了。
2.2.2 配置
经过初始化阶段,我们就拥有了每个项目的对象了,每个项目都一一对应一个build.gradle
脚本文件,在配置阶段就会加载执行此文件,此文件的操作对象正是Project
的实例对象,如上图所示为DefaultProject_Decorated
这个实现类对象,比如我们定义了依赖,或者插件,然后这个项目实例对象就知道需要解析的依赖,插件有哪些,这被称为为项目配置,因此叫配置阶段。 配置完(执行完build.gradle)就会根据配置为每个项目创建执行任务图TaskExecutionGraph
对象,供下一步进行调用。
还记得任务的概念吗,就是执行一个步骤的最小单元,通俗点说就是编译,测试等这些步骤,当然更准确的定义是前面的,因为除了这些编译测试打包我们还可以自己定义任务。
// 注册一个任务 TaskA
tasks.register("TaskA"){
// Action行为,即做什么,先打印一句话
doFirst {
println "Hello Gradle!"
}
// 任务后执行
doLast {
println "哈哈哈哈哈"
}
}
// 注册一个任务 TaskB
tasks.register("TaskB"){
// 任务先执行
doFirst {
println "TaskB执行"
}
dependsOn("TaskA")
}
// 查看当前项目的执行任务图
gradle.taskGraph.whenReady {graph->{
graph.allTasks.each { task -> {
println task.name
}}
}}
输出
执行build.gradle
的时候就包括依赖解析,这一步属于“执行build.gradle”的阶段,不算是任务Task。
总结:配置阶段会执行每个项目的build.gradle
脚本,为每个项目实例进行配置,然后生成任务执行图TaskExecutionGraph
实例对象
其实到这里,我估计很多人都好奇“你是怎么知道build.gradle”中可以写什么的? 这属于build.gradle
配置脚本编写的问题,这里先记住或者理解生命周期的每个阶段都做了什么。
2.2.3 执行任务链
根据前面生成的任务执行图(TaskExecutionGraph)对象然后根据任务链依次执行任务,整个构建就结束了。
查看build
任务的执行任务图
// gradle.taskGraph返回的就是TaskExecutionGraph对象
gradle.taskGraph.whenReady {graph->{
graph.allTasks.each { task -> {
println task.name
}}
}}
输出
可以看到执行build
会先经历编译,打包,测试等一系列任务,而之所以一个任务执行会包括另一个任务就是之前的自定义任务中的小例子,这叫任务依赖Task Dependency
,执行build
任务需要依赖另一个任务,则这个任务会先执行。
2.3 理解.gradle
脚本
Gradle
脚本是配置脚本, 脚本总归需要一个东西来执行,而这个执行器则规定了配置的类型,比如每个项目对应的build.gradle
他的执行器指定的类型就是Project
,因此你可以在build.gradle
中使用任何在Project
这个接口中定义的属性和方法。
从生命周期整体观看,Gradle
构建项目只涉及三种脚本,初始化脚本init.gradle
,项目配置的setting.gradle
和构建项目的build.gradle
。
对应关系如下:
Type of script | Delegates to instance of |
---|---|
Build script | Project |
Init script | Gradle |
Settings script | Settings |
注意:脚本都是以.gradle
结尾,名称并不固定,如果是kotlin
语言通常还是.gradle.kts
这都不重要,你需要知道的是,构建脚本并不是一定需要叫build.gradle
如果你在编写build.gradle
文件,那当前文件就可以理解为一个Project对象,里边的方法和属性可以随便使用。
如果你在编写init.gradle
文件,那么当前文件就可以理解为一个Gradle
对象,里边的方法和属性可以随便使用。
如果你在编写settings.gradle
文件,那么当前文件就可以理解为一个Settings
对象,里边的方法和属性可以随便使用。
另外Groovy
语言完全支持Java
库,也就是说Java
中怎么写的代码,在Groovy
脚本中都可以写!
以Build script
为例,这类脚本用于配置项目,那我怎么知道可以怎么写呢,答案是查看Project
这个接口的API。
比如这里定义了静态属性,那我就可以在build.gradle
中直接使用
build.gradle
println DEFAULT_BUILD_FILE
println DEFAULT_BUILD_DIR_NAME
println DEFAULT_VERSION
输出
groovy
语法:方法只要不是空参,都可以省略()
即printlin('hello')等价println 'hello'
再比如我定义依赖
dependencies {
// https://mvnrepository.com/artifact/org.mybatis/mybatis
implementation group: 'org.mybatis', name: 'mybatis', version: '3.5.16'
}
我是怎么知道依赖是denpendencies
而不是denpendency
呢,答案还是API
,这是在Project
这个接口中定义的方法。
我现在已经知道在build.gradle
中可以使用Project
的任意属性和方法了,那我又怎么知道denpendencies
里面应该写什么呢?答案还是查看API
,可以看到上面的方法已经声明了参数是Closure
,这是groovy
的语法,叫闭包,这也是一个类,只不过是Groovy
提供的。
在Gradle
中,Closure
算是比较老的写法了,新的Gradle API
提供的都是Action
类,作用是完全一样的,但是因为加入了范型可以更好的理解,正确的定义类似这样Action<? extents Task>
我们就知道参数闭包的类型是Task
了,再回到denpencies(Closure configureClosure)
我怎么知道你的闭包是什么类型?
针对参数为Closure
的,没有很好的办法,一个是通过IDEA
的智能,直接点击跳转,另一办法就是查看官方文档提供的说明,参考👉DenpendencyHandler
此处参数Closure
的实例类型为DenpendencyHandler
,但你继续去看DenpendencyHandler API
的时候实际上找不到方法implementation
,这是因为这个方法由插件java
提供,但是大致已经可以猜到,存在某个类实现了DenpendencyHandler
接口并且提供了implementation
这个方法了。
再看个好点的例子,以配置镜像仓库为例。
/**
* 1️⃣Project接口定义的方法 void allprojects(@DelegatesTo(Project.class) Closure configureClosure);
* @DelegatesTo 表明这个闭包参数类型为 Project,因此 allprojects{}这个里面可以用Project中的方法
*/
allprojects {
/**
* 2️⃣Project接口定义的方法 void repositories(Closure configureClosure);
* 参数为Closure并不友好,我们只知道是闭包,并不知道具体哪个类,只能查看文档,或者利用IDEA,在repositories{}里边使用.得到提示
* 实际参数类型为:RepositoryHandler
*/
repositories {
/**
* 3️⃣RepositoryHandler接口定义的方法 maven(Action<? super MavenArtifactRepository> action);
* Action<? super MavenArtifactRepository> 表明maven的参数类型是 MavenArtifactRepository
* 通过查看 MavenArtifactRepository 这个接口API我们就知道为什么需要写url了,并且类似的我们也可以写其他的
* 比如:artifactUrls {}
*/
maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
maven {
url 'https://maven.aliyun.com/repository/central'
artifacts {}
}
mavenCentral()
}
}
截图
以上就是build.gradle
写法的基本思路!
2.3.1 依赖管理
前面已经提到了denpendencies{}
的用法了,这里直接全部讲清楚。
在Gradle
中依赖分为三种:
- 本地依赖(就是从文件系统查找)
- 项目依赖(基于当前项目的依赖)
- 直接依赖(最常见的从仓库下载的依赖)
dependencies {
// 本地依赖
implementation files("lib/asm-3.3.1.jar")
// 项目依赖
implementation(':sub-project3')
// 直接依赖
implementation('org.mybatis:mybatis:3.5.16')
}
输出
其中implementation
是方法,对于方法的()
可加可不加,如果没有参数则必须加。
对于依赖的写法则直接在Maven
仓库搜索然后复制粘贴即可。
同Maven
中的pom.xml
一样,一个依赖包括groupId
,artifactId
,version
以及作用范围scope
。
一个完整的gradle
依赖格式为:
<scope> (groupId:groupId,artifactId:artifactId,version:version)
// 其中括号可以省略,方法名称也可以省略,就成了这种简短方式 <== groovy 语法
<scope> 'groupId:artifactId:version'
其中<scope>
包括:
implementation
,作用范围为编译和运行时都需要,跟pom.xml
中不写是一样的。compileOnly
,作用范围是仅编译有效runtimeOnly
,作用范围仅运行有效testImplementaion
,测试类编译和运行有效testCompileOnly
,测试类编译有效testRuntimeOnly
,测试类运行有效api
等同implementation
除了api
这些都是由核心插件java
提供的,因此使用都需要先导入插件! 参考👉 核心插件java
api
由核心插件java-library
提供,使用时候需要先导入依赖java-library
。
// 如果你使用`IDEA`创建一个新项目,应该会在build.gradle文件中看到默认添加了此插件
plugins {
id 'java'
}
dependencies {
testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
}
⚡️api和implementation
的区别
api
引入的依赖会暴露给他的消费者,换句话说,如果模块A使用api
依赖了mybatis
那么我定义一个模块B
依赖模块A就会间接依赖mybatis
,而通过implementaion
依赖,则不会传递这个依赖。
具体看图
2.3.2 源码分析
以spring-framework
源码为例,分析build.gradle
配置脚本。
注释
/**
* 定义在PluginManagementSpec接口中
*
* abstract fun plugins(action: Action<in PluginDependenciesSpec>)
* 由此可知 plugins{} 内部语法可以使用PluginDependenciesSpec类中任意方法和属性
*
* 参考文档:https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.plugin.management/-plugin-management-spec/plugins.html
*/
plugins {
id 'io.freefair.aspectj' version '8.4' apply false
// 省略很多
}
/**
* 扩展Project接口中的属性,必须为ext,通过ext设置的属性moduleProjects和javaProjects在任何Project中都可以访问
*
* 参考文档:https://docs.gradle.org/current/dsl/org.gradle.api.Project.html
* subjects是Project接口提供的方法可以直接使用
*
* findAll{} 中it代表findAll的参数(这里为Project实例对象),这是`groovy`语法,参数可以指定或者不写,不写默认为it
* 比如:tasks.each{task->{
* println task.name }
* }
* 等价:tasks.each{
* println it.name
* }
*/
ext {
moduleProjects = subprojects.findAll { it.name.startsWith("spring-") }
javaProjects = subprojects.findAll { !it.name.startsWith("framework-") }
}
// Project中定义的属性,这里直接赋值
description = "Spring Framework"
// Project中定义的方法configuration,用于配置项目对象Project
configure(allprojects) { project ->
apply plugin: "org.springframework.build.localdev"
group = "org.springframework"
repositories {
mavenCentral()
maven {
url "https://repo.spring.io/milestone"
content {
// Netty 5 optional support
includeGroup 'io.projectreactor.netty'
}
} if (version.contains('-')) {
maven { url "https://repo.spring.io/milestone" }
}
if (version.endsWith('-SNAPSHOT')) {
maven { url "https://repo.spring.io/snapshot" }
}
}
configurations.all {
resolutionStrategy {
cacheChangingModulesFor 0, "seconds"
cacheDynamicVersionsFor 0, "seconds"
}
}
}
3 技巧
3.1 配置下载源
同maven
一样,下载依赖时候先从本地找,本地没有就从远程仓库下载,gradle
一个道理,不过更加灵活。
3.1.1 配置gradle
镜像
gradle是软件包,不是仓库依赖包,配置maven
仓库地址对这个是无效的,注意区别软件和依赖的概念!
直接复制下面内容替换distributionUrl
distributionUrl=https://mirrors.cloud.tencent.com/gradle/gradle-8.5-bin.zip
其他镜像
https://mirrors.aliyun.com/macports/distfiles/gradle/
配置初始化脚本自动替换
如果每次创建一个项目都要手动修改一次配置文件未免也太麻烦了,我需要找到备忘录,复制镜像地址然后修改原地址,因此写了下面的初始化脚本。
init.gradle
/**
* <h2>自动配置`gradle`下载镜像</h2>
* <p>原理是`gradle`生命周期会优先查找初始化脚本(此文件)加载,因此需保证此文件能正确加载!</p>
* * <p>gradle查找初始化脚本规则如下:</p>
* <ol>
* <li>命令行参数指定初始化脚本,`./gradlew build -I init.gradle`,其中`init.gradle`就是你写的初始化脚本,`-I`等价`--init-script`</li>
* <li>添加一个文件`init.gradle`在`$GRADLE_USER_HOME/`目录下</li>
* <li>添加一个文件`init.gradle`在`$GRADLE_USER_HOME/init.d/`目录下</li>
* <li>添加一个文件`init.gradle`在`$GRADLE_HOME/init.d/`目录下</li>
* </ol>
* <p>按照顺序依次查找,其中`$GRADLE_USER_HOME`默认就是<b>(~/.gradle或者C:\Users\<USERNAME>\.gradle)</b>目录,也就是当前用户所在
* 目录下.gradle/目录,`$GRADLE_HOME`是你安装时候配置的环境变量,没有就是没配。</p>
*
* <p>官网文档链接 https://docs.gradle.org/current/userguide/init_scripts.html</p>
* <h2>镜像地址</h2>
* <p>备选: https://mirrors.aliyun.com/macports/distfiles/gradle/</p>
*/
def mirrorUrl = "https://mirrors.cloud.tencent.com/gradle/"
checkOrReplace(mirrorUrl)
gradle.taskGraph.whenReady { graph ->
graph.allTasks.each { task ->
if (task.name == "wrapper") {
task.distributionUrl = mirrorDistributeUrl
logger.warn("执行`gradle wrapper`任务生成 gradle/wrapper 目录:已自动替换分发URL ==> {}",mirrorDistributeUrl)
}
}
}
def checkOrReplace(mirrorUrl){
String filePath = "gradle/wrapper/gradle-wrapper.properties"
def propertiesFile = new File(filePath)
def mirrorDistributeUrl
if (propertiesFile.exists()) {
def props = new Properties()
props.load(propertiesFile.newInputStream())
def originUrl = props.get("distributionUrl")
def fileName = originUrl.substring(originUrl.lastIndexOf("/") + 1)
mirrorDistributeUrl = mirrorUrl + fileName
if (!checkConnect(mirrorDistributeUrl)) {
return
}
if (!propertiesFile.canWrite()) {
logger.error("无权写入文件:{}",filePath)
return
}
if (mirrorDistributeUrl != originUrl) {
props.setProperty("distributionUrl",mirrorDistributeUrl)
props.store(propertiesFile.newOutputStream(),null)
logger.warn("gradle下载链接已更换:{}",mirrorDistributeUrl)
}
}
}
def checkConnect(String checkUrl){
def url = new URL(checkUrl)
def httpConnection = (HttpURLConnection)url.openConnection()
httpConnection.setRequestMethod("GET")
httpConnection.connect()
if (httpConnection.responseCode != 200) {
logger.warn("镜像链接无效:{}",checkUrl)
return false
}
return true
}
确保这个文件被正确加载,放在此目录下即可👇
看到打印已加载初始化脚本 init.gradle. 就说明加载了。
3.1.1 配置依赖仓库镜像
原理是gradle
的初始化脚本,在构建项目之前gradle
都会优先执行初始化脚本,这种方式属于全局配置。
规则如下:
- 命令行参数指定初始化脚本,
./gradlew build -I init.gradle
,其中init.gradle
就是你写的初始化脚本,-I
等价--init-script
- 添加一个文件
init.gradle
在$GRADLE_USER_HOME/
目录下 - 添加一个文件
init.gradle
在$GRADLE_USER_HOME/init.d/
目录下 - 添加一个文件
init.gradle
在$GRADLE_HOME/init.d/
目录下
$GRADLE_USER_HOME
默认就是(~/.grade或者C:\Users\<USERNAME>\.gradle)
目录,也就是当前用户所在目录了,$GRADLE_HOME
是你安装时候配置的环境变量,没有就是没配。
init.gradle
allprojects {
repositories {
mavenLocal()
maven { name 'Alibaba' url 'https://maven.aliyun.com/nexus/content/groups/public/' }
maven { name 'Bstek' url 'https://maven.aliyun.com/nexus/content/groups/public/' }
}}
注释版
def PUBLIC = "https://maven.aliyun.com/nexus/content/groups/public/"
def CENTRAL = "https://maven.aliyun.com/repository/central"
def GRADLE_PLUGIN = "https://maven.aliyun.com/repository/gradle-plugin"
// 项目所需要的依赖 API参考 --> Project类
allprojects {
repositories {
// 1.先从本地缓存查找 $GRADLE_USER_HOME/.gradle/caches/modules-2/files-2.1 mavenLocal()
// 2.再从镜像下载,配置参考:https://developer.aliyun.com/mvn/guide
maven { url PUBLIC }
maven { url CENTRAL }
maven { url GRADLE_PLUGIN }
// 3.最后再走中央仓库
mavenCentral()
}
}
// 脚本文件(当前及其他.gradle文件)所需要的依赖 API参考 --> HandleScript类
buildscript {
repositories {
// 1.先从本地缓存查找
mavenLocal()
// 2.再从镜像下载,配置参考:https://developer.aliyun.com/mvn/guide
maven { url PUBLIC }
maven { url CENTRAL }
maven { url GRADLE_PLUGIN }
// 3.最后再走中央仓库
mavenCentral()
gradlePluginPortal()
}
}
阿里云仓库配置指南:https://developer.aliyun.com/mvn/guide
将文件添加在此目录
下载的依赖存放在.gradle/caches/module-2/file-2.1/
目录下,这个目录其实都叫缓存目录,mavenLocal()
本地查找就是查找这个目录。
Q&A
Plugin下载失败
如果一直提示下载插件失败,可以尝试下面的解决方案:
删除缓存(就是上面的目录)
rm -rf ~/.gradle/caches
刷新依赖
./gradlew --refresh-dependencies
或者
./gradlew -U
cas-initializr无法解析依赖
错误信息
* What went wrong:
Could not determine the dependencies of task ':ui:nodeSetup'.
> Failed to query the value of task ':ui:nodeSetup' property 'nodeArchiveFile'.
> Could not resolve all files for configuration ':ui:detachedConfiguration1'.
> Could not resolve org.nodejs:node:22.5.1.
Required by:
project :ui
> Could not resolve org.nodejs:node:22.5.1.
> Could not get resource 'https://nodejs.org/dist/v22.5.1/node-v22.5.1-darwin-arm64.tar.gz'.
> Could not HEAD 'https://nodejs.org/dist/v22.5.1/node-v22.5.1-darwin-arm64.tar.gz'.
> Read timed out
解决方案
添加nodejs
的镜像链接,注意是软件包不是依赖仓库maven
的镜像链接!
为什么知道在这里添加?错误信息提示了项目为ui
,任务为nodeSetup
,这个由插件node-gradle
提供,查询此插件得知node{}
里边可以设置distBaseUrl
。
node {
version = '22.5.1'
yarnVersion = ''
download = true
distBaseUrl.set("https://mirrors.huaweicloud.com/nodejs/")
}
spring-framework构建项目失败
错误信息
1: Task failed with an exception.
-----------
* What went wrong:
A problem occurred configuring project ':integration-tests'.
> Failed to calculate the value of task ':integration-tests:compileJava' property 'javaCompiler'.
> Unable to download toolchain matching the requirements ({languageVersion=17, vendor=BELLSOFT, implementation=vendor-specific}) from 'https://api.foojay.io/disco/v3.0/ids/ccea34bde715d3405bd8237de4f51ad4/redirect'.
> Could not HEAD 'https://github.com/bell-sw/Liberica/releases/download/17.0.12+10/bellsoft-jdk17.0.12+10-macos-aarch64-lite.tar.gz'.
> Connect to github.com:443 [github.com/140.82.112.3] failed: Connect timed out
解决方案
这个是由于toolchain
查找JDK
没有找到指定的然后去下载JDK
时,因为安装的JDK
信任库不包括github.com
(也可能因为我使用Dev-side
加速添加的那个证书导致),whatever
需要那就手动导入github
的证书到信任库吧。
获取证书:
导入证书:
keytool -import -alias 'github' -keystore /Library/Java/JavaVirtualMachines/jdk-17.0.2.jdk/Contents/Home/lib/security/cacerts -file github.com.pem
注意替换为自己的JDK
安装目录和指定自己的证书文件,其中需要输入口令,默认changeit
。
spring-framework
源码工程构建成功。