Gradle入门

起因

之前拉取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这个cleanbuild就是任务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这个包装器。

在这里插入图片描述

  1. gradle-wrapper.jar就是这个东西负责下载和安装gradle
  2. gradle-wrapper.properties定义了下载gradle的网址和安装目录
  3. gradlewMac/Linux用户的脚本文件
  4. gradlew.batWindows用户的脚本文件

配置文件

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,可以看到包括runbuildclean这种,其实本质就是命令了。

好比mvn install命令,这些任务Task也是存在依赖关系的,像我们Java代码,都需要先编译,那么我们构建之前,就会自动执行编译先,这就是Task denpendency任务依赖的概念。

在这里插入图片描述

1.6 插件 Plugins基础

就像上面说的Task,插件主要目的就是扩展这种功能让gradle可以执行更多的task选项和配置。

在这里插入图片描述

1.6.1 插件来源

插件主要包括三类:

  1. 核心插件;这主要是gradle官方的。
  2. 社区插件;比如spring-boot为了支持gradle构建项目由spring提供的插件,这种第三方插件就是社区插件。
  3. 本地插件;就是根据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构建项目的流程,主要包括三个阶段:

  1. 初始化:加载初始化脚本,执行setting.gradle脚本,根据此脚本查找所有项目,为每个项目创建一个Project的实例对象,便于下一步配置。
  2. 配置:执行每个项目的buidl.gradle构建脚本,这个构建脚本就包括了每个项目所需要的依赖,而这个过程我们称之为为当前项目Project配置,并生成一个执行任务图(TaskExecutionGraph)
  3. 执行:按照执行任务图,开始执行每个Task。

其实在初始化阶段还包括一点:查找初始化脚本(官网没有特别说明)!

官网图示
在这里插入图片描述

我们一定要清楚一点,Gradle是基于Java写的,他不像pom.xml那样根据约束进行纯文本的配置,因此里边存在很多Object对象的概念,项目被抽象为类Project,而要操作这个类,自然就需要实例化对象。

2.2.1 初始化

在执行./gradlew build的时候,首先会加载初始化脚本,即.gradle结尾的文件,查找规则如下:

  1. 命令行参数指定初始化脚本,./gradlew build -I init.gradle,其中init.gradle就是你写的初始化脚本,-I等价--init-script
  2. 添加一个文件init.gradle$GRADLE_USER_HOME/目录下
  3. 添加一个文件init.gradle$GRADLE_USER_HOME/init.d/目录下
  4. 添加一个文件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就可以知道下一步需要实例化的Projectroot根项目,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 scriptDelegates to instance of
Build scriptProject
Init scriptGradle
Settings scriptSettings

注意:脚本都是以.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中依赖分为三种:

  1. 本地依赖(就是从文件系统查找)
  2. 项目依赖(基于当前项目的依赖)
  3. 直接依赖(最常见的从仓库下载的依赖)
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都会优先执行初始化脚本,这种方式属于全局配置。

规则如下:

  1. 命令行参数指定初始化脚本,./gradlew build -I init.gradle,其中init.gradle就是你写的初始化脚本,-I等价--init-script
  2. 添加一个文件init.gradle$GRADLE_USER_HOME/目录下
  3. 添加一个文件init.gradle$GRADLE_USER_HOME/init.d/目录下
  4. 添加一个文件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源码工程构建成功。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值