Gradle 笔记
Gradle依赖管理(基于Kotlin DSL)
**注意:**如果不是工作原因或是编写安卓项目必须要用Gradle,建议学习Maven即可,Gradle的学习成本相比Maven高很多,而且学了有没有用还是另一回事,所以,作为过来人我不希望浪费大家这几个小时的时间。
欢迎各位小伙伴来到Gradle的学习课程,在开始本课程之前,您需要完成以下前置课程:
- SpringBoot教程: 本Gradle教程全篇以构建Java项目为例进行讲解,建议至少学习到SpringBoot及之后再观看。
- Kotlin程序设计教程: Gradle脚本基于Kotlin DSL编写,必须掌握Kotlin程序设计基础知识。
- Maven依赖管理教程: 推荐先了解一下更简单的Maven依赖管理,会更加容易理解。
- (可选)SpringCloud视频教程: 包含对于多模块项目的介绍。
Gradle构建工具是一个快速、可靠和适应性强的开源构建自动化工具,具有优雅和可扩展的声明性构建语言,Gradle包含许多优势:
- Gradle是JVM平台最受欢迎的构建系统,也是Android和Kotlin多平台项目的默认选择,它拥有丰富的社区插件生态系统。
- Gradle可以使用其内置函数、第三方插件或自定义构建逻辑自动执行各种软件构建场景。
- Gradle提供了一种高级、声明式和富有表现力的构建语言,使阅读和编写构建逻辑变得很轻松。
- Gradle快速、可扩展,可以构建任意规模和复杂度的项目。
- Gradle产生可靠的结果,同时受益于增量构建、构建缓存和并行执行等优化。
Gradle支持Android、Java、Kotlin Multiplatform、Groovy、Scala、Javascript和C/C++等热门语言的构建工作:
所有常见的IDE都支持Gradle,包括Android Studio、IntelliJ IDEA、Visual Studio Code、Eclipse和NetBeans。
接下来就让我们开始学习Gradle吧。
走进Gradle
这一部分我们来学习Gradle的安装和初始化。
安装Gradle环境
在使用Gradle之前,我们先来看看如何安装,这里演示Windows环境下,如何进行安装,其他平台请参考官方文档:https://docs.gradle.org/current/userguide/installation.html#installation
在开始安装之前,首先确保您已经安装好Java环境(使用java --version
命令进行查看)版本不能低于Java 8,本教程使用的是Java 17版本:
确认无误后,我们进入到Gradle官网下载最新版本:下载地址,这里我们选择直接下载已经编译好的二进制文件:
注意: 本教程教学采用的是8.5版本,如果各位小伙伴在学习时官方已经推出了新的版本,请务必选择跟教程相同的版本,因为Gradle的版本兼容性很差,不同版本出现特性变化的情况很常见,容易出现不兼容的情况。
下载完成后解压,然后将解压得到的目录拖到你想要安装的位置,本教程就放到C:\Program Files
目录下:
接着我们需要配置相应的环境变量才能使用,打开环境变量配置面板:
添加环境变量GRADLE_HOME
,变量值为刚刚存放的Gradle文件夹位置。接着我们在Path环境变量中添加对应的bin目录:
添加完成后,我们打开一个新的命令窗口,输入gradle -v
命令查看是否配置生效:
可以看到当前的Gradle相关版本信息已经打印到控制台了。
注意: 如果配置没有生效,可以尝试重启电脑或者查看是否路径编写错误。
初始化Gradle项目
安装完成Gradle之后,我们就可以开始正式进行学习了,首先我们还是来尝试创建一个Gradle项目。
这里我们创建一个新的空文件夹用于测试:
接着,我们在当前目录打开CMD窗口,并执行以下命令:
gradle init
出现以下内容,让我们选择当前项目的类型,这里我们选择2
应用类型:
> Task :wrapperSelect type of project to generate:1: basic2: application3: library4: Gradle plugin
Enter selection (default: basic) [1..4] 2
接着是选择我们程序使用的语言类型,本教程统一使用Java语言讲解,选择3
号:
> Task :initSelect implementation language:1: C++2: Groovy3: Java4: Kotlin5: Scala6: Swift
Enter selection (default: Java) [1..6] 3
接着是是否生成多个子项目结构,我们先按照最简单的单个项目进行讲解,这里直接选择no
即可:
Generate multiple subprojects for application? (default: no) [yes, no] no
接着是编写Gradle脚本采用的语言,目前Gradle支持Kotlin和Groovy两种,且目前官方推荐的是Kotlin语言,所以这里就选择1
号:
Select build script DSL:1: Kotlin2: Groovy
Enter selection (default: Kotlin) [1..2] 1
Gradle还需要配置一个测试使用的框架,这里我们选择默认的就行:
Select test framework:1: JUnit 42: TestNG3: Spock4: JUnit Jupiter
Enter selection (default: JUnit Jupiter) [1..4]
接着是当前项目的名称以及包名,默认就是当前目录名字,这个自己根据情况选择吧:
Project name (default: Test):Source package (default: test): com.test
最后是采用的Java版本,因为这里我们安装的是Java17,所以直接输入17即可,还有是否选择使用新特性直接no
就好了:
Enter target version of Java (min. 7) (default: 17): 17Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no) [yes, no]
接着我们可以看到项目成功完成了初始化操作:
> Task :init
To learn more about Gradle by exploring our Samples at https://docs.gradle.org/8.5/samples/sample_building_java_applications.htmlBUILD SUCCESSFUL in 12m 38s
2 actionable tasks: 2 executed
现在回到我们的文件夹中,可以看到已经生成了大大小小的文件了:
注意: 不同版本的Gradle对我们使用的IDEA版本同样存在兼容性问题,这里推荐使用IntelliJ IDEA 2023.3
或更高版本,旧版本的IDEA可能会不支持当前的Gradle 8.5版本。
我们可以直接在IDEA中打开这个项目,打开后正常情况下会自动开始初始化,初始化过程中会下载很多东西,包括Gradle当前版本本身,以及它所需要的所有依赖包以及Kotlin相关库:
不对啊,我们本地不是已经安装了Gradle吗,怎么初始化项目的时候又要下载一次呢?这是因为Gradle Wrapper的作用,由于Gradle的跨版本兼容性很差,因此它指定了当前项目中使用的Gradle版本,使得不同的开发人员或CI/CD系统都能使用相同的Gradle版本来构建项目,从而提高项目的一致性和可移植性。Gradle Wrapper的另一个好处是,它可以自动下载和使用正确版本的Gradle,无需手动安装或配置Gradle,使得项目的维护和协作更加方便,后续别人拿到我们这个项目的时候,不需要自己在系统中部署Gradle环境。
这个过程中可能会很卡,因为服务器在国外(建议挂梯)直到出现BUILD SUCCESSFUL
之后,表示初始化完成:
此时我们的项目目录里面已经存在这些内容了:
我们来依次介绍一下这些内容分别代表什么以及有什么作用:
.gradle
:Gradle自动生成的项目缓存目录。.idea
:这个是IDEA的项目配置目录,跟Gradle生成的没关系,无视掉就行。app
:存放整个项目的源代码、测试等,这里面就是我们写代码的地方了。build.gradle.kts
:项目的gradle构建脚本。src
:存放源代码和测试代码。main
:编写所有项目核心代码。test
:编写项目测试代码。
gradle
:包含JAR文件和Gradle Wrapper的配置。gradlew
:适用于macOS和Linux的使用Gradle Wrapper执行构建的脚本(这里的版本就是GradleWrapper指定的版本)gradlew.bat
:适用于Windows的使用Gradle Wrapper执行构建的脚本。settings.gradle.kts
:定义子项目列表的项目配置文件,也是最关键的设置文件。
除了以上文件以外的其他文件,一般都是一些额外的Git文件,例如.gitignore
,不属于Gradle的范畴,无视掉就可以了。
Gradle项目在生成时默认为我们创建了一个测试的主类:
/** This Java source file was generated by the Gradle 'init' task.*/
package com.test;public class App {public String getGreeting() {return "Hello World!";}public static void main(String[] args) {System.out.println(new App().getGreeting());}
}
我们可以在IDEA中尝试直接运行:
这样,咱们的第一个Gradle项目就成功创建并运行了。
Gradle常用命令
我们在一开始就说了,Gradle最主要的目的就是为了构建大型项目,其中构建项目的常用命令非常关键,我们这节课就来学习一下。
首先我们可以查看一下所有Gradle支持的任务,这里我们使用GradleWapper提供的gradlew
进行操作,使用方式其实和gradle
命令是一样的,只是这里用的是生成的(注意Windows平台下需要使用gradlew.bat来运行)
./gradlew.bat task
./gradlew task
其中包含了大量的命令操作:
比如第一个run
命令就可以将当前项目以JVM应用程序的形式运行:
这个命令会自动编译并运行我们的项目,我们也可以手动执行其中的每一步,现在就来尝试一下吧,首先先执行一次清理命令,将整个项目目录进行一次清理,主要清理掉的就是构建出来的文件:
./gradlew clean
我们在编写好一个Java项目之后,第一步就是编译成class文件,然后才能开始运行,所以,我们可以使用以下命令来编译代码:
./gradlew classes
./gradlew classes -q #安静模式,只执行,不打印日志
执行完成之后,如果出现BUILD SUCCESSFUL
表示编译成功,此时在app目录下会生成一个新的build目录,此目录中存放的就是编译之后的相关文件了,其中classes目录存放的就是所有编译之后的class文件:
有些时候我们的项目可能需要在编译之后运行一些测试用例,来快速查看是否存在一些问题,这在开发过程中非常常见,我们可以看到Gradle默认情况下为我们生成了一个简易的测试用例:
class AppTest {//默认使用的是JUnit作为单元测试工具@Test void appHasAGreeting() {App classUnderTest = new App();//这里是判断如果结果为Null的话就抛出异常assertNotNull(classUnderTest.getGreeting(), "app should have a greeting");}
}
我们可以将App类中的getGreeting方法的返回值设置为null试试看:
public class App {public String getGreeting() {return null;}...
}
使用以下命令来执行测试:
./gradlew test
可以看到测试失败了,并且Gradle还自动为我们生成了一个错误报告,其中明确指出了我们出现错误的测试用例:
我们也可以快速执行一个完整的编译+测试流程来构建整个项目,并得到打包好的jar等文件,使用以下命令来完成:
./gradlew build
如果某些时候我们不想执行测试,只想构建整个项目,也可以添加参数跳过:
./gradlew build -x test
只不过这样去敲命令实在是太累了,在IDEA中已经自动帮助我们继承了Gradle插件,我们可以直接展开右侧工具栏,双击就能使用Gradle命令了,非常方便:
项目配置
前面我们介绍了Gradle项目的搭建与使用,我们先来看一下整个Gradle项目构建的流程:
大概清楚构建流程后,我们接着就来了解一下Gradle项目是如何进行配置的。
配置文件介绍
设置文件settings.gradle
是整个Gradle项目的入口点:
设置文件用于定义所有的子项目,并让它们参与到构建中,Gradle支持单项目和多项目的构建:
- 对于单个项目构建,设置文件是可选的。
- 对于多项目构建,设置文件必须存在,并在其中定义所有子项目。
设置文件可以使用Groovy语言(名称为settings.gradle
)或是Kotlin语言(名称为settings.gradle.kts
)编写,本教程一律采用Kotlin语言进行讲解,设置文件通常位于项目的根目录中:
一个标准的Gradle设置文件按照以下样式进行编写,这里使用Kotlin语言介绍:
rootProject.name = "root-project" //rootProject对象代表当前这个项目,其name属性就是当前项目的名称include("sub-project-a") //所有的子项目使用include()函数进行添加,如果没有子项目可以不写
include("sub-project-b")
include("sub-project-c")
接着我们来看针对于单个项目的构建文件,其中gradle.build
文件就是对应的构建配置,这里我们使用的是Kotlin语言,因此项目中会存在一个gradle.build.kts
文件:
plugins {id("java")
}group = "cn.itbaima"
version = "1.0-SNAPSHOT"repositories {mavenCentral()
}dependencies {testImplementation(platform("org.junit:junit-bom:5.9.1"))testImplementation("org.junit.jupiter:junit-jupiter")
}tasks.test {useJUnitPlatform()
}
可以看到,在这个配置文件中存在大量的Lambda语句,这也使得整个配置文件写起来更加简介美观,所以说虽然Gradle依赖于JVM平台,但是仅支持Kotlin和Groovy,它们相比Java在语法上存在更大的优势,更适合编写这种类似脚本一样的配置文件。
我们首先来看最顶上的plugins函数,后面的Lambda中编写了当前项目需要使用到的插件:
plugins {id("java")
}
使用id()
函数指定需要使用的插件,这里使用的是java
插件,java
插件将Java编译以及测试和捆绑功能添加到项目中,为建任何类型的 Java 项目提供支持。
当然,除了java插件之外,我们如果需要构建其他类型的项目,也可以使用多种多样的插件:
plugins {id("cpp-application") //用于构建C++应用程序
}
plugins {id("swift-application") //用于在MacOS构建Swift应用程序
}
plugins {id("org.jetbrains.kotlin.jvm") version "1.9.0" //使用version中缀函数为id指定的插件添加版本
}
有关插件相关的内容,我们会在后面的部分深入为大家介绍,不同的插件会为Gradle添加不同的任务。
这里的group和version分别设置了当前项目所属的组名称和版本:
group = "cn.itbaima"
version = "1.0-SNAPSHOT"
接着是所有依赖的仓库配置,默认使用的是Maven中心仓库:
repositories {mavenCentral()
}
接着是所有的依赖列表,这里默认为我们导入了JUnit相关依赖用于测试:
dependencies {testImplementation(platform("org.junit:junit-bom:5.9.1"))testImplementation("org.junit.jupiter:junit-jupiter")
}
最后是任务相关配置,这里对test
任务进行了相关配置:
tasks.test {useJUnitPlatform()
}
从下节课开始我们来详细介绍一下各个部分。
编写设置文件
我们前面介绍了Gradle构建的大致流程和配置文件各个部分,而设置文件settings.gradle.kts
则是整个构建的入口点,在Gradle构建生命周期的早期,初始化阶段会在项目根目录中找到设置文件。
当找到设置文件settings.gradle(.kts)
时,Gradle会实例化一个Settings
对象,我们可以通过此对象来声明要包含在构建中的所有项目,包括我们项目名称的声明也是通过它来完成:
settings.rootProject.name = "untitled"
我们也可以省略掉settings直接使用其提供的属性:
rootProject.name = "untitled"
其中,Settings对象包含以下常用属性:
姓名 | 描述 |
---|---|
buildCache | 项目构建所用缓存配置。 |
plugins | 用于设置的插件。 |
rootDir | 项目构建的根目录,根目录是整个项目目录最外层。 |
rootProject | 构建的根项目。 |
settings | 返回设置对象。 |
Settings对象也包含以下方法可供调用:
姓名 | 描述 |
---|---|
include() | 将指定名称的项目添加到构建列表中。 |
includeBuild() | 添加指定路径上的其他Gradle项目到构建列表中。 |
我们在编写配置文件时,本质上是对Gradle API的一系列方法调用,结合Groovy或Kotlin的{...}
语法(在Groovy中称作闭包,Kotlin中称为Lambda)能够轻松编写非常简洁的配置,比如配置插件:
rootProject.name = "untitled"plugins { //使用plugins函数配置插件,结合Lambda表达式可以使用简洁语法完成配置id("test") //瞎写的一个
}
在配置文件中定义的语句,Gradle会一行一行地向下执行,就像一个脚本那样。实际上,我们编写的配置文件在执行Gradle构建时会编译为对应的class文件加载执行,这些文件存放在Gradle的缓存目录中:
因此,我们直接在Gradle配置中编写的自定义语句也可以执行:
这里我们执行了自定义的打印语句,包括可以通过rootProject
对象拿到当前的项目文件File对象等。
在Gradle中,和Maven一样也分为插件和依赖,我们可以在settings.gradle.kt
中可以为所有的项目进行统一配置,比如要修改获取插件的仓库位置:
pluginManagement { //使用pluginManagement函数配置插件仓库列表repositories { //在repositories函数中配置需要用的仓库gradlePluginPortal() //Gradle插件仓库google() //Google插件仓库}
}
我们也可以修改为国内的阿里云镜像:
pluginManagement {repositories {//手动指定maven仓库地址,修改URL地址maven {setUrl("https://maven.aliyun.com/repository/public/")}}
}
同样的,对于所有的依赖,也可以直接配置为国内的阿里云镜像仓库:
dependencyResolutionManagement { //依赖解析管理中可以配置全局依赖仓库repositories { //只不过这种方式目前还在孵化阶段,可能会在未来某个版本移除maven {setUrl("https://maven.aliyun.com/repository/public/")}}
}
不过,除了在settings.gradle.kts
中配置三方仓库之外,我们更推荐在之后学习的build.gradle.kts
中对仓库进行配置。
与Maven一样,Gradle插件可以帮助我们在构建过程中实现各种各样的高级功能,它是用于增加和扩展任务(tasks)或构建逻辑的一种特殊类型的模块,在settings.gradle.kts
中可以配置插件,只不通常被用于需要对多个项目进行操作的插件,或者需要在构建开始之前进行一些配置的情况:
plugins { //使用id明确插件名称,使用version中缀函数明确插件版本id("org.gradle.toolchains.fake") version "0.6.0"
}
一般很少见有项目在这里配置插件。
对于多个子项目的大型项目而言,我们还需要在这里添加所有的子项目:
include("app")
include("business-logic")
include("data-model")
有关多项目的详细介绍,我们会放在下一个章节,本章节主要以单项目为主。
除了以上提到的内容,Settings
对象上还有更多属性和方法,您可以使用它们来配置构建。不过,虽然许多Gradle脚本通常以简短的Groovy或Kotlin语法编写,但设置脚本中的每个操作都是在Settings
对象上调用方法:
include("app")
settings.include("app") //实际上是这样的,只是说settings可以省掉
编写构建脚本
前面我们介绍了设置文件的编写,它是我们整个项目的构建起点,包含了所有基本内容的配置。
我们接着来看针对于我们每一个项目的构建脚本build.gradle.kts
文件:
对于设置文件中包含的每个项目,Gradle都会为其创建一个Project
实例对象,我们可以直接在build.gradle.kts
中使用,跟之前一样,可以省略:
group = "cn.itbaima"
version = "1.0-SNAPSHOT"
project.group = "cn.itbaima" //本质上就是project的属性
project.version = "1.0-SNAPSHOT"
在此对象中,包含以下常见属性:
姓名 | 类型 | 描述 |
---|---|---|
name | String | 项目目录的名称。 |
path | String | 该项目的完全限定名称。 |
description | String | 该项目的描述。 |
dependencies | DependencyHandler | 配置项目的依赖列表。 |
repositories | RepositoryHandler | 配置项目的依赖仓库。 |
layout | ProjectLayout | 通过此对象来访问项目中的关键位置。 |
group | Object | 项目的组。 |
version | Object | 项目的版本。 |
我们依次来看每个部分是如何进行编写的。
首先是整个项目所采用的插件,我们可以像下面这样编写:
plugins {id("org.jetbrains.kotlin.jvm") version "1.9.0"id("application")
}
其中,插件application
是由官方内置的插件,可以直接使用id()
函数来选择,而上面的org.jetbrains.kotlin.jvm
插件没有被官方内置,需要我们手动使用version
中缀函数指定其版本,这样Gradle才能在仓库中正确找到它。
一般情况下,我们普通的Java项目可以直接使用java
插件,它能够直接完成编译和打包Java代码:
plugins {id("java")
}
我们也可以对这个插件进行一些配置,比如我们要生成目标的Java版本等:
configure<JavaPluginExtension> {targetCompatibility = JavaVersion.VERSION_17 //编译目标版本sourceCompatibility = JavaVersion.VERSION_17 //源代码版本(会导致只能使用对应版本具有的特性,如果使用更高版本的特性或语法将无法通过编译)
}
如果我们需要将项目打包为一个可执行的文件,也可以使用application
插件,它包含java
插件的全部功能,同样支持编译和打包Java代码,并且支持生成可执行的应用程序:
plugins {id("application")
}java { //configure<JavaApplication> 也可以直接写成 java 这个扩展函数,效果一样targetCompatibility = JavaVersion.VERSION_17sourceCompatibility = JavaVersion.VERSION_17
}application { //同configure<JavaApplication>mainClass = "com.test.Main" //配置主类
}
配置完成后,我们直接执行build
构建项目,生成以下文件:
可以看到这里生成的压缩包的形式,我们可以直接解压,得到一系列文件:
可以看到,在bin目录中已经生成了用于运行我们Java项目的脚本(Mac/Linux是项目名称的脚本,Windows是bat脚本)然后在lib目录中是已经帮我们打包好的项目jar文件,如果我们项目中还存在其他的依赖,也会包含在这个lib中。我们可以直接运行:
这跟我们前面学习的Maven其实非常相似,同样可以通过插件实现打包,只不过这里是采用编程的形式配置,而Maven统一采用XML形式进行配置。
接着我们来看如何配置项目中的依赖项,首先是对于依赖仓库的选择,默认情况下生成的代码选择的是Maven中心仓库:
repositories {mavenCentral() //Maven中心仓库google() //谷歌仓库
}
我们可以自己定义,比如只使用本地仓库中的包:
repositories {mavenLocal() //只使用Maven本地仓库中的软件包
}
我们可以通过maven
函数来直接指定第三方仓库:
repositories {maven {//修改其url属性来指定一个第三方仓库地址setUrl("https://maven.aliyun.com/repository/public/")}
}
配置好仓库地址后,我们就可以开始添加需要的依赖了,在dependencies
中进行编写,默认情况下添加了一些测试相关的依赖:
dependencies {testImplementation(platform("org.junit:junit-bom:5.9.1"))testImplementation("org.junit.jupiter:junit-jupiter")
}
这里我们可以使用两种不同的函数导入依赖:
implementation
导入依赖,用于编译和运行生产代码。testImplementation
导入依赖,用于编译和运行测试代码。
其中填写的字符串就是我们依赖的组、名称以及其对应的版本号:
org.junit:junit-bom:5.9.1
对应的组为org.junit
,依赖名称为:junit-bom
,版本号为:5.9.1
我们可以尝试一下添加自己需要的依赖,比如这里我们添加一个Spring框架的核心依赖,首先我们可以在这里找到需要的包:https://central.sonatype.com,找到后选择:
然后直接粘贴到依赖列表里面即可:
//使用implementation来添加依赖
implementation("org.springframework:spring-context:6.1.3")
更新后,就可以直接使用了:
@Configuration
public class Main {@Beanpublic String string() {return "Hello World!";}public static void main(String[] args) {ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);System.out.println(context.getBean(String.class));}
}
使用起来跟Maven其实差别不是很大。
当然,除了像这样写在一起之外,我们也可以分开进行编写,把组、名称和版本填写为三个参数:
implementation("org.springframework", "spring-context", "6.1.3")
我们有些时候希望一直使用最新版本的依赖,也可以直接将版本设置为+
来始终使用最新版本:
implementation("org.springframework:spring-context:+")
//生产环境下不推荐这样写,万一新版改了啥东西导致项目出大问题就得不偿失了
下一节我们将继续深入依赖的相关配置,了解更多配置细节。
深入依赖配置
上一部分我们介绍了如何导入依赖,我们接着来看导入依赖之后的更多操作。
除了通过直接编写Maven坐标的形式来引入仓库中的依赖之外,我们也可以直接将一个本地的Jar包引入到项目中,这里我们在项目根目录下新建一个lib
目录,用于存放我们需要引用的Jar包:
接着我们在依赖中编写引入的语句:
dependencies {//使用files方法来指定一个文件进行导入implementation(files("lib/spring-context-6.1.3.jar", "lib/spring-core-6.1.3.jar", "lib/spring-beans-6.1.3.jar", "lib/spring-jcl-6.1.3.jar"))...
}
public static void main(String[] args) {ApplicationContext context = new FileSystemXmlApplicationContext();
}
如果我们一个目录下的jar包太多,需要全部导入,像上面这样一个一个写太累了,我们可以直接使用fileTree方法来统一获取:
implementation(fileTree("lib")) //直接引入lib下全部jar包
效果和上面一个一个导入一样。
我们接着来看依赖的排除,在之前Maven的学习中我们知道,一个依赖的内部可能又会存在多个依赖,在使用一些依赖时,我们可能希望排除其中某些不需要的依赖或是与其他依赖冲突的依赖,我们可以对依赖进行排除操作:
implementation("org.springframework:spring-context:6.1.3") {//在Lambda中使用exclude来排除不需要的依赖exclude("org.springframework", "spring-aop")
}
这里我们在引入spring-context
之后,不需要其中的AOP模块,因此我们可以直接使用exclude函数将其排除,此时AOP模块就不存在于外部库中了:
我们再来看下面这种情况:
implementation("org.springframework:spring-context:6.1.3")
implementation("org.springframework:spring-aop:6.1.1")
此时我们的项目中不仅依靠spring-context
引入了spring-aop
的6.1.3版本,也手动引入了spring-aop
的6.1.1版本,此时Gradle会优先使用更新的版本作为依赖,所以这里实际引入使用的也是6.1.3版本。
如果我们执意要使用旧版本的依赖,可以通过上面的方式进行依赖的排除,或是给版本号添加感叹号表示强制使用:
implementation("org.springframework:spring-context:6.1.3") {exclude("org.springframework", "spring-aop") //直接排除掉新版本的
}
implementation("org.springframework:spring-aop:6.1.1")
implementation("org.springframework:spring-context:6.1.3")
implementation("org.springframework:spring-aop:6.1.1!!") //添加双感叹号
最后我们来列举一下DependencyHandlerScope里面包含的方法:
implementation
: 用于添加项目的依赖项,这是最常用的方法。api
: 与implementation
类似,但它会暴露依赖项给项目的所有模块(多项目配置中讲解)compileOnly
: 用于指定编译时依赖,但不会在运行时包含在最终构建结果中。testImplementation
: 用于添加测试时需要的依赖项。androidTestImplementation
: 用于添加Android测试时需要的依赖项。kapt
: 用于添加 Kotlin 注解处理器依赖项。annotationProcessor
: 用于添加 Java 注解处理器依赖项。runtimeOnly
:仅在运行时使用,不用于编译
相信各位小伙伴对于implementation
已经非常熟悉了,它其实是Java插件提供的方法,我们可以使用它来引入依赖,并且在打包之后也会附带我们引入的依赖:
我们来看看其他的导入方式,首先是compileOnly
,它表示导入的依赖仅在编译时可用,编译完成后不会将依赖一起打包:
compileOnly("org.springframework:spring-context:6.1.3")
接着是runtimeOnly
,它表示导入的依赖仅在运行时可用,比如MySQL驱动这类我们不需要在项目中直接使用,但是在项目运行时又需要用到的依赖就非常适合:
runtimeOnly("org.springframework:spring-context:6.1.3")
此时,由于此依赖仅存在于运行时,项目中是无法使用的:
但是我们去掉这些不可用的代码,在编译之后,这些依赖却会被打包在一起。
自定义任务
Gradle可以在项目上完成的工作由一个或多个任务定义,任务代表构建执行的某些独立工作单元,比如编译一些类,创建jar包,生成Javadoc文档,或将一些内容发布到代码仓库,这些任务通常由插件提供,我们在项目中引入插件后就可以直接执行对应的任务了,在不引入任何插件的情况下,只会包含一些内置的默认任务:
在引入java
插件后,就出现了各种Java相关的任务,比如编译、构建、打包jar包等:
可以看到,这些任务都是由插件为我们提供的,因此,大部分情况下我们引入插件之后就可以直接执行相关的任务了。我们在执行某一个大任务的时候,就会执行一系列的任务,比如build
命令,它不仅完成构建,还将所有的任务依赖关系进行完整的建立,可以看到:
在整个build任务执行的过程中,依次按顺序执行了各种各样的任务:
- compileJava:编译所有Java源代码文件。
- processResources:处理所有资源文件,由于这个项目没有资源文件,提示 NO-SOURCE
- …
通过这一系列小任务,就完成了编译、构建、打包等一系列操作,只需要执行:./gradlew build
即可。
当然,如果各位小伙伴觉得插件提供的任务不太够,需要自定义添加,我们也可以在build.gradle.kts
中编写自定义任务:
注册任务需要使用register
或create
函数来完成,一个最简单的任务可以像下面这样编写:
tasks.register("hello") { //第一个参数为任务名称,第二个参数使用Lambda编写任务具体操作//任务包含一个完整的操作列表,我们需要传入对应的Action到队列中,这样就可以依次执行了doFirst { //使用doFirst向任务队列首部插入新的Action,也就是要执行的内容println("我是自定义的任务开始")}doLast { //向队列尾部插入Actionprintln("我是自定义的任务结束")}
}
可以看到,刷新之后任务列表就出现了我们自定义的任务,执行之后也是我们自定义的内容:
我们也可以通过指令的形式直接运行:
在执行命令时,我们还可以添加额外的项目参数,在脚本中可以直接获取:
tasks.register("hello") {println("获取到自定义参数: ${project.properties["test"]}")...
}
我们还可以配置此任务所属的组别,以及其它描述信息:
我们还可以将一个任务作为gradle
执行的默认任务,也就是说直接执行gradle命令就可以运行我们的任务了:
defaultTasks("hello")
一个任务还可以依赖于其他任务,比如我们的自定义任务在执行之前需要先完成源代码的编译操作:
tasks.register("hello") {group = "build"description = "这是一个非常伟大的任务!"dependsOn(tasks.compileJava) //使用dependsOn函数来指定前置任务,可以是其他插件提供的,也可以是我们自己定义的,这个参数也可以传字符串...
}
在执行时,会先完成前置任务之后再执行当前任务:
我们也可以让已经存在的任务来依赖我们的任务或是直接为其添加额外的操作:
tasks.named("build") { //根据名称进行查找dependsOn("hello") //直接配置依赖doLast { ... } //添加新的Action到列表中
}tasks.build { //直接从tasks中获取,这仅限于插件提供的任务dependsOn("hello")
}
这样,我们就为任务build
添加了前置任务到前置任务列表中:
在Gradle中,实际上所有的任务都是Task的子类,除了向上面这样直接编写Task类型,包括我们在前面使用的register
方法,默认也是在Lambda中为我们提供一个Task类型的对象:
@Override
TaskProvider<Task> register(String name, Action<? super Task> configurationAction) throws InvalidUserDataException;
//这里Action的默认提供类型就是Task
我们也可以自行创建Task的子类,来编写自定义的任务类型:
// 继承 DefaultTask 类来创建一个自定义的 HelloTask 类,注意这个类必须要可继承,要么open要么直接抽象类
open class HelloTask : DefaultTask() {private var name: String = ""@TaskAction //添加@TaskAction注解来声明此函数为任务的Actionfun hello() {println("${name}: 卡布奇诺今犹在,不见当年倒茶人")}fun user(name: String) { //类中也可以具有其他的函数this.name = name}
}tasks.register<HelloTask>("hello") { //使用register时指明我们自定义的任务类user("卢本伟") //此时this就是HelloTask类型了,我们可以直接使用自定义的函数
}
通过这种方式也可以实现任务的自定义:
除了通过插件或是我们自定义的形式编写任务之外,Gradle也为我们提供了一些内置的任务类型,这些任务通常是一些经常会用到的操作,我们可以来使用一下,比如复制任务:
tasks.register<Copy>("hello") { //这里使用Copy类型from("build/classes") //使用from和into设置复制的目录和目标位置into("test")dependsOn(tasks.build) //依赖一下build
}
执行我们自定义的hello任务,就可以完成构建 + 拷贝了:
除了这里演示才Copy操作,开发人员还可以利用许多任务类型,包括GroovyDoc
、Zip
、Jar
、JacocoReport
、Sign
或Delete
等。
生命周期钩子
有些时候我们希望在Gradle的整个生命周期中的不同时段执行一些操作,我们可以使用官方提供的生命周期钩子函数。
- 构建初始阶段
gradle.settingsEvaluated()
完成项目的配置阶段之后调用(只能定义在 setting.gradle 或 init.gradle 脚本中)gradle.projectsLoaded()
所有项目加载之后调用(只能定义在 setting.gradle 或 init.gradle 脚本中)
- 配置阶段
gradle.beforeProject()
每个项目完成配置之前调用(只能定义在 setting.gradle 或 init.gradle 脚本中)gradle.afterProject()
每个项目完成配置之后调用gradle.projectEvaluated()
所有项目全部完成配置之后调用gradle.afterEvaluate()
整个配置阶段完成后调用gradle.taskGraph.whenReady
全部任务图已经构建完成可以就绪后调用
- 执行阶段
gradle.taskGraph.beforeTask
执行每一个任务之前调用gradle.taskGraph.afterTask
每一个任务执行完成之后调用gradle.buildFinished
整个构建全部结束后调用
这里我们可以尝试编写一个钩子函数试试看:
gradle.settingsEvaluated {println("开始构建")
}gradle.buildFinished {println("构建结束")
}
这样,我们就可以在不同的阶段执行自定义的内容了。比如,我们可以利用这种特性来统计某一个阶段或是任务耗费的时间:
var time: Long = 0
gradle.taskGraph.beforeTask {time = System.currentTimeMillis()
}gradle.taskGraph.afterTask {val takeTime = System.currentTimeMillis() - timeprintln("任务: $name 执行耗时: ${takeTime}ms")
}
得到的结果就会自动计算每个任务的执行时间了:
项目发布
我们也可以将自己的的Gradle项目发布到的Maven仓库中,这里我们以发布到本地仓库为例:
plugins {id("java")id("maven-publish") //首先引入maven-publish插件
}
接着我们来配置发布相关的内容:
publishing {publications {create<MavenPublication>("library") { //发布的相关信息Maven坐标信息groupId = "cn.itbaima"artifactId = "hello"version = "0.0.1"from(components["java"]) //发布为jar包形式}}repositories {mavenLocal() //指定为本地Maven仓库}
}
接着我们执行publish
任务即可发布项目到本地仓库了:
此时在.m2
目录中已经存在我们发布的项目了:
创建SpringBoot项目
通过IDEA或是前往SpringBoot官网就可以自动为我们创建一个基于Gradle的SpringBoot项目:https://start.spring.io/
创建完成后,使用IDEA打开会自动下载GradleWrapper指定的版本,并同时下载相关的插件:
这里自动生成了项目的build.gradle.kts
文件:
plugins { //首先是插件配置java //最基础的Java插件id("org.springframework.boot") version "3.2.1" //SpringBoot插件id("io.spring.dependency-management") version "1.1.4" //Spring依赖管理插件
}group = "cn.itbaima" //不用说了吧
version = "0.0.1-SNAPSHOT"java {sourceCompatibility = JavaVersion.VERSION_17 //配置Java源代码编译版本
}configurations {compileOnly { //注解处理相关配置,用于LombokextendsFrom(configurations.annotationProcessor.get())}
}repositories { //使用Maven中心仓库mavenCentral()
}dependencies { //包含SpringBoot相关依赖,以及Lombok依赖implementation("org.springframework.boot:spring-boot-starter-web")compileOnly("org.projectlombok:lombok")annotationProcessor("org.projectlombok:lombok")testImplementation("org.springframework.boot:spring-boot-starter-test")
}tasks.withType<Test> {useJUnitPlatform()
}
相信各位小伙伴现在应该能够看懂SpringBoot项目为我们自动生成的Gradle构建文件了。
JVM语言混合编程
对于某些需要使用多种JVM语言进行编程的项目,我们可以同时配置多种插件,这里我们以Java与Kotlin混合编程为例进行讲解:
plugins { //同时配置Java和Kotlin/JVM插件id("java")kotlin("jvm") version "1.9.21"
}
编写好插件后,我们直接刷新项目,接着就可以在src下的main和test目录中同时创建两个源代码目录:
- java:存放Java源代码文件
- kotlin:存放Kotlin源代码文件
这里我们的项目已经是一个Java项目了,我们可以直接右键main
点击新建目录,然后直接选择Gradle集源中的kotlin目录:
此时就可以在kotlin目录下编写Kotlin相关的源代码了:
//位置:src/main/kotlin/com/test/Student.ktpackage com.testdata class Student(var name: String, var age: Int)
//位置:src/main/java/com/test/Main.java
package com.test;public class Main {public static void main(String[] args) {Student student = new Student("小明", 18);System.out.println(student);}
}
编写好之后,点击main方法旁边的运行按钮,可以看到一起编译了Java和Kotlin源代码,并成功运行:
如果要打包为可执行的应用,也可以直接使用application
插件:
plugins {id("application")kotlin("jvm") version "1.9.21"
}
打包之后,已经自动将Kotlin标准库依赖集成了,也可以直接使用:
这样,我们就可以愉快地进行混合编程了。
多项目配置
前面我们介绍的是单个项目的布局,现在我们接着来看多个项目的布局。
通常对于一些大型的分布式项目来说,我们会在一个项目中包含多个模块,不同的模块负责不同的功能,并且不同模块的代码独立进行编写,这时整个Gradle项目就会存在多个子项目:
large-project
|
├── auth-service
│ ...
│ └── build.gradle.kts
├── chat-service
│ ...
│ └── build.gradle.kts
└── settings.gradle.kts
可以看到,在根项目下,只存在settings.gradle.kts
用于全局配置,而具体的build.gradle.kts
构建文件存在于各个子项目中分别进行编写。
我们也可以按照这样的结构去搭建我们的多模块项目。
项目创建
要创建一个多模块项目,我们可以先使用IDEA创建一个简单的Gradle项目出来:
接着我们删除不需要的内容,比如:src
目录、build.gradle.kts
文件等,接着右键最外层项目,创建一个新的子项目,此时我们需要选择最外层项目为我们的父项目:
创建完成后,我们可以打开最外层的settings.gradle.kts
文件,IDEA已经自动为我们添加了模块引入:
rootProject.name = "large-project"
include("auth-service")
此时,父子项目的层级已经明确划分出来了:
可以看到,build.gradle.kts
现在由子模块进行配置,每个子模块都可以单独处理。
在存在多个子项目的时候,我们可以直接在根项目中执行指令,会生效于全部的子项目:
可以看到,这里的项目名称前面都加了一个:
符号。
我们也可以配置项目之间的依赖,我们只需在dependencies
中进行配置:
dependencies {implementation(project(":common")) //使用project方法来引用其他项目作为依赖,项目名称前需要添加单引号testImplementation(platform("org.junit:junit-bom:5.9.1"))testImplementation("org.junit.jupiter:junit-jupiter")
}
有些时候,我们可能需要将一些部分统一进行配置,比如配置代码仓库地址,以及项目用到的插件,我们可以直接在根项目创建一个build.gradle.kts
来统一编写内容:
subprojects { //subprojects表示对所有的子项目生效apply(plugin = "java") //定义插件需要使用apply来完成,plugin{}在这里不行group = "cn.itbaima" //定义组version = "unspecified" //定义版本repositories { //定义自定义仓库地址maven {setUrl("https://maven.aliyun.com/repository/public/")}}
}
这样,我们即使不在子项目中编写这些,就可以直接得到根项目的配置:
dependencies {testImplementation(platform("org.junit:junit-bom:5.9.1"))testImplementation("org.junit.jupiter:junit-jupiter")
}tasks.test {useJUnitPlatform()
}
依赖的传递
我们在使用多模块时可能会遇到这样的一个问题,现在有三个模块,并且具有以下依赖关联:
- service-a
- service-b implementation(service-a)
- service-c implementation(service-b)
此时,从链式的依赖关系上来看,它们长这样:service-a -> service-b -> service-c
按照正常的传递关系来说,在B中应该是可以直接使用A中定义的内容的,因为依赖了A模块,同时,由于C依赖了B模块,那么理所应当,C也应该可以直接使用A中定义的内容,我们来看看是否真的如此:
//A模块中定义
public class Test {public static void test() {System.out.println("Hello World!");}
}
//B模块中定义
public class Main {public static void main(String[] args) {Test.test();}
}
//C模块中定义
public class Main {public static void main(String[] args) {Test.test(); //找不到类Test,编译失败}
}
这就很奇怪了,在Maven中是完全可以实现传递依赖的,为什么到Gradle就不行了呢?这是因为implementation
不支持依赖传递,因此,即使我们改变了B模块的依赖,此时C也无法通过传递的形式得到B包含的依赖,由于不需要处理传递依赖,在编译时只需要处理一层,因此速度会非常快,大部分情况下非常推荐使用implementation
来进行依赖导入。
如果各位小伙伴需要实现传递依赖的效果,我们需要使用另一个插件提供的方法来导入依赖:
plugins {`java-library` //java-library提供了传递依赖api函数
}dependencies {api(project(":common")) //与implementation效果一样,但是支持传递依赖testImplementation(platform("org.junit:junit-bom:5.9.1"))testImplementation("org.junit.jupiter:junit-jupiter")
}
此时,模块B的依赖由于使用了api进行导入,我们在模块C中无论是使用api
导入还是implementation
导入B,都可以使用B中api函数导入的依赖了。
使用buildSrc模块
在Gradle中有一个特殊机制,我们可以创建一个名为buildSrc
的模块,它支持我们自己定义能够在kts
脚本中使用的内容。
这里我们直接创建这样的一个项目:
注意在创建之后我们需要移除settings.gradle.kts
中的包含关系:
rootProject.name = "large-project"
include("auth-service", "chat-service", "common")
include("buildSrc") //不能作为子项目存在
接着我们修改一下build.gradle.kts
文件:
plugins {`kotlin-dsl`
}repositories {mavenCentral()
}
此时我们就可以编写我们需要使用的部分了,比如我们希望吧阿里云Maven仓库封装成一个函数的形式,直接使用,就像mavenCentral()
那样,不然每次都要写一个完整地址,很麻烦,这里我们直接开一个新的kt文件编写扩展函数:
import org.gradle.api.artifacts.dsl.RepositoryHandler//由于repositories函数中提供的this为RepositoryHandler类型的对象
//这里我们直接为其编写扩展函数
fun RepositoryHandler.mavenAlibaba() = maven {setUrl("https://maven.aliyun.com/repository/public/")
}
接着,我们就可以在其他的kts脚本中使用我们定义的内容了,比如根项目的配置:
subprojects {apply(plugin = "java")group = "cn.itbaima"version = "unspecified"repositories {mavenAlibaba() //直接使用自定义的}
}
是不是感觉很方便?我们也可以使用这种方式定义所有需要使用的依赖版本,实现统一管理,比如编译版本的管理:
import org.gradle.api.JavaVersionobject Version {val sourceVersion = JavaVersion.VERSION_17val targetVersion = JavaVersion.VERSION_17
}
subprojects {...configure<JavaPluginExtension> {targetCompatibility = Version.targetVersion //直接使用Version类获取版本sourceCompatibility = Version.sourceVersion}
}
包括如果我们需要使用一些第三方的依赖,也可以统一管理版本:
object Version {...val springVersion = "6.1.3" //统一Spring相关依赖的版本
}
dependencies {implementation("org.springframework:spring-beans:${Version.springVersion}") //直接从Version中获取版本implementation("org.springframework:spring-aop:${Version.springVersion}")implementation("org.springframework:spring-web:${Version.springVersion}")testImplementation(platform("org.junit:junit-bom:5.9.1"))testImplementation("org.junit.jupiter:junit-jupiter")
}
除了以上用法之外,我们也可以在buildSrc模块中编写自定义的插件并使用,这里我们创建一个新的插件类:
package com.pluginimport org.gradle.api.Plugin
import org.gradle.api.Projectclass MyPlugin: Plugin<Project> {override fun apply(target: Project) { //插件应用时会直接调用apply函数println("插件生效了")}
}
接着我们需要在buildSrc的构建脚本中声明此插件:
gradlePlugin {plugins {create("my-custom-plugin") { //创建一个新的Pluginid = "my-plugin" //插件的IDimplementationClass = "com.plugin.MyPlugin" //插件的实现类}}
}
声明之后,我们就可以在项目中使用了:
plugins {...id("my-plugin") //直接通过id使用插件
}
利用插件,我们可以快速为项目添加各种任务,这里我们魔改一下上面写的插件:
class MyPlugin: Plugin<Project> {override fun apply(target: Project) { //直接在apply中注册新的任务,这样插件加载之后就具有这些任务了target.tasks.register("a") {doLast { println("你干嘛") }}target.tasks.register("b") {doLast { println("哎哟") }dependsOn(target.tasks.named("a"))}}
}
此时项目中已经存在我们自定义的两个任务了:
相信大家也应该明白为什么官方插件导入之后会多出来这么多任务了。
相关文章:

Gradle 笔记
Gradle依赖管理(基于Kotlin DSL) **注意:**如果不是工作原因或是编写安卓项目必须要用Gradle,建议学习Maven即可,Gradle的学习成本相比Maven高很多,而且学了有没有用还是另一回事,所以ÿ…...
flume案例
在构建数仓时,经常会用到flume接收日志数据,通常涉及到的组件为kafka,hdfs等。下面以一个flume接收指定topic数据,并存入hdfs的案例,大致了解下flume相关使用规则。 版本:1.9 Source Kafka Source就是一…...

信用评价研究MATLAB仿真代码
信用评价是各种店铺卖家分析买家信用行为的重要内容, 本文给出随机仿真代码模拟实际交易过程的信用评价. 主要研究内容有: (1)研究最大交易额和信用度的关系 (2)研究买家不评价率对信用度影响 (3)研究交易次数对信用度影响 MATLAB程序如下: 主程序main.m %% clc;close a…...
网络安全产品之认识防毒墙
在互联网发展的初期,网络结构相对简单,病毒通常利用操作系统和软件程序的漏洞发起攻击,厂商们针对这些漏洞发布补丁程序。然而,并不是所有终端都能及时更新这些补丁,随着网络安全威胁的不断升级和互联网的普及…...
android 防抖工具类,经纬度检查工具类
一:点击事件防抖工具类: public abstract class ThrottleClickListener implements View.OnClickListener {private long clickLastTimeKey 0;private final long thresholdMillis 500;//millisecondsOverridepublic void onClick(View v) {long curr…...

PgSQL - 17新特性 - 块级别增量备份
PgSQL - 17新特性 - 块级别增量备份 PgSQL可通过pg_basebackup进行全量备份。在构建复制关系时,创建备机时需要通过pg_basebackup全量拉取一个备份,形成一个mirror。但很多场景下,我们往往不需要进行全量备份/恢复,数据量特别大的…...
Vue3setup()的非语法糖和语法糖的用法
1、setup()的语法糖的用法 script标签上写setup属性,不需要export default {} setup() 都可以省 创建每个属性或方法时也不需要return 导入某个组件时也不需要注册 <script setup > // script标签上写setup属性,不需要export default {} set…...
HTTP状态信息
1xx: 信息 消息:描述:100 Continue服务器仅接收到部分请求,但是一旦服务器并没有拒绝该请求,客户端应该继续发送其余的请求。101 Switching Protocols服务器转换协议:服务器将遵从客户的请求转换到另外一种协议。 2xx: 成功 消息:描述:200…...

CSS之边框样式
让我为大家介绍一下边框样式吧!如果大家想更进一步了解边框的使用,可以阅读这一篇文章:CSS边框border 属性描述none没有边框,即忽略所有边框的宽度(默认值)solid边框为单实线dashed边框为虚线dotted边框为点线double边框为双实线 代码演示&…...

k8s-helm
Helm: 什么是helm,在没有这个heml之前,deployment service ingress的作用就是通过打包的方式,把deployment service ingress这些打包在一块,一键式的部署服务,类似于yum 官方提供的一个类似于安全仓库的功能,可以实现…...

黑马程序员JavaWeb开发|Maven高级
一、分模块设计与开发 分模块设计: 将项目按照功能拆分成若干个子模块,方便项目的管理维护、扩展,也方便模块间的相互调用,资源共享。 注意:分模块开发需要先对模块功能进行设计,再进行编码。不会先将工…...

【经验分享】MAC系统安装R和Rstudio(保姆级教程)安装下载只需5min
最近换了Macbook的Air电脑,自然要换很多新软件啦,首先需要安装的就是R和Rstudio啦,网上的教程很多很繁琐,为此我特意总结了最简单实用的安装方式: 一、先R后Rstudio 二、R下载 下载网址:https://cran.r-project.org …...

探索设计模式的魅力:“感受单例模式的力量与神秘” - 掌握编程的王牌技巧
在软件开发的赛场上,单例模式以其独特的魅力长期占据着重要的地位。作为设计模式中的一员,它在整个软件工程的棋盘上扮演着关键性角色。本文将带你深入探索单例模式的神秘面纱,从历史渊源到现代应用,从基础实现到高级技巧…...

SpringCloud Aliba-Seata【上】-从入门到学废【7】
目录 🧂.Seata是什么 🌭2.Seata术语表 🥓3.处理过程 🧈4.下载 🍿5.修改相关配置 🥞6.启动seata 1.Seata是什么 Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能…...

C# Cad2016二次开发选择csv导入信息(七)
//选择csv导入信息 [CommandMethod("setdata")] //本程序在AutoCAD的快捷命令是"DLLLOAD" public void setdata() {Microsoft.Win32.OpenFileDialog dlg new Microsoft.Win32.OpenFileDialog();dlg.DefaultExt ".csv";// Display OpenFileDial…...

[陇剑杯 2021]日志分析
[陇剑杯 2021]日志分析 题目做法及思路解析(个人分享) 问一:单位某应用程序被攻击,请分析日志,进行作答: 网络存在源码泄漏,源码文件名是_____________。(请提交带有文件后缀的文件名&…...

Java面试汇总——jvm篇
目录 JVM的组成: 1、JVM 概述(⭐⭐⭐⭐) 1.1 JVM是什么? 1.2 JVM由哪些部分组成,运行流程是什么? 2、什么是程序计数器?(⭐⭐⭐⭐) 3、介绍一下Java的堆(⭐⭐⭐⭐) 4、虚拟机栈(⭐⭐⭐⭐) 4.1 什么是虚拟机栈&…...

数据结构:完全二叉树(递归实现)
如果完全二叉树的深度为h,那么除了第h层外,其他层的节点个数都是满的,第h层的节点都靠左排列。 完全二叉树的编号方法是从上到下,从左到右,根节点为1号节点,设完全二叉树的节点数为sum,某节点编…...

RK3568 移植Ubuntu
使用ubuntu-base构建根文件系统 1、到ubuntu官网获取 ubuntu-base-18.04.5-base-arm64.tar.gz Ubuntu Base 18.04.5 LTS (Bionic Beaver) 2、将获取的文件拷贝到ubuntu虚拟机,新建目录,并解压 mkdir ubuntu_rootfs sudo tar -xpf u...

C++大学教程(第九版)6.34猜数字游戏 6.35 修改的猜数字游戏
文章目录 6.34题目代码运行截图6.35题目代码运行截图 6.34题目 猜数字游戏)编写一个程序,可以玩“猜数字”的游戏。具体描述如下:程序在1~1000之间的整数中随机选择需要被猜的数,然后显示: 代码 #include <iostream> #include <cstdlib>…...

超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...

MODBUS TCP转CANopen 技术赋能高效协同作业
在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南
🚀 C extern 关键字深度解析:跨文件编程的终极指南 📅 更新时间:2025年6月5日 🏷️ 标签:C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言🔥一、extern 是什么?&…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...
MySQL 8.0 事务全面讲解
以下是一个结合两次回答的 MySQL 8.0 事务全面讲解,涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容,并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念(ACID) 事务是…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...