Gradle进阶学习:深入了解Task的魔力
🤔 前言:Gradle中的Task——你会用,但了解它的秘密吗?
你已经在使用Gradle来构建项目,享受它的便利与高效。那么,你知道在Gradle中,Task是如何发挥作用的?它不仅仅是一个执行命令的容器,还是Gradle强大功能的核心!今天,我们就来深入探讨一下Gradle中的Task,看看它如何帮助我们更高效、更灵活地构建项目。做好准备,这不仅是技术的分享,更是一次从“懂”到“精”的进阶之旅!
在日常的开发中,Gradle已经成为了许多Java开发者的得力助手。从简单的构建脚本到复杂的依赖管理,Gradle都能轻松搞定。但如果你真的想深入了解它,那么Task这个概念就必须掌握。它不仅决定了构建过程中的任务如何执行,还能帮助你灵活控制和优化整个构建流程。那你可能会问,Task真的有那么神奇吗?放心,接下来我会带你一起探索它的奥秘!
🚀 Task的基本概念:它是什么?
在Gradle中,Task 是构建过程的最小单元。可以把它想象成一个“工作单元”,你可以在构建脚本中定义各种任务来执行不同的操作。一个Task可能是编译代码、打包文件、运行测试、清理项目等。这些任务会按顺序执行,或者根据不同的依赖关系来安排。简单来说,Task就是你写构建脚本时,想让程序做的每一个具体动作。
比如你写了一个简单的任务,它只是打印一条信息:
task hello {
doLast {
println 'Hello, Gradle!'
}
}
代码解析:
接着我将对上述代码逐句进行一个详细解读,希望能够帮助到同学们,能以最快的速度对其知识点掌握于心,这也是我写此文的初衷,授人以鱼不如授人以渔,只有将其原理摸透,日后应对场景使用,才能得心应手,如鱼得水。所以如果有基础的同学,可以略过如下代码解析,针对没基础的同学,还是需要加强对代码的逻辑与实现,方便日后的你能更深入理解它并常规使用不受限制。
在这段代码中,我们创建了一个名为hello
的任务,任务的执行内容是打印一句话“Hello, Gradle!”。注意这里的doLast
,它是Gradle中用于定义任务执行的闭包。doLast
表示任务执行的最后一部分,但如果你需要在任务的开始做些准备工作,可以使用doFirst
。至于doLast
,它通常用于任务执行的主要工作。
这段代码是一个简单的 Gradle 构建脚本,使用 Groovy 语言编写。它定义了一个名为 hello
的任务,并在任务执行时打印一条消息。下面是详细解析:
解析代码:
-
任务定义:
task hello
:定义了一个名为hello
的任务。任务是 Gradle 构建流程中的基本单元,定义了构建步骤的执行过程。每个任务都可以指定要执行的动作或操作。
-
任务的执行动作:
doLast { ... }
:这是任务的一个操作块,表示在任务的末尾执行的操作。在doLast
块内,可以编写在任务完成时需要执行的逻辑。每个任务都可以有多个doLast
操作块,这些块会按照顺序执行。- 在
doLast
块中,println 'Hello, Gradle!'
语句会打印出Hello, Gradle!
到控制台。
任务执行:
- 你可以在命令行中通过执行
gradle hello
来运行该任务。当你运行这个命令时,Gradle 会调用hello
任务,执行doLast
中的操作,即打印出Hello, Gradle!
。
使用示例:
如果你有一个 Gradle 项目,在项目根目录下的 build.gradle
文件中添加这段代码,然后在命令行中运行以下命令:
gradle hello
你将看到输出:
Hello, Gradle!
扩展:
- 你可以为任务添加更多的逻辑,比如文件操作、编译代码、执行测试等。
doLast
是 Gradle 中用于定义任务最后要执行的操作之一,另一种常用的操作块是doFirst
,它在任务开始执行时先被调用。
总结:
这段代码展示了如何在 Gradle 中定义和执行一个简单任务,在任务完成时打印一条信息。Gradle 任务的定义非常灵活,可以根据需要在任务中添加多种操作来满足构建过程的需求。
🔗 Task的依赖关系:互相配合才能更强大 💪
说到构建任务,很多时候它们并不是孤立执行的。在复杂的构建过程中,一个任务可能会依赖于其他任务的结果。这时候,Gradle的任务依赖关系就显得尤为重要。想象一下,假如你正在做一道菜,而切菜是必须在煮菜之前完成的。那么,显然煮菜的任务就依赖于切菜任务的完成。同样的道理,在Gradle中,我们可以通过dependsOn
关键字来定义任务之间的依赖关系。
举个例子,假设我们需要在构建之前清理项目,那么我们可以这样设置:
task clean {
doLast {
println 'Cleaning project...'
}
}
task build {
dependsOn clean
doLast {
println 'Building project...'
}
}
在这段代码中,我们定义了两个任务,clean
和build
。任务build
依赖于clean
,也就是说在执行build
之前,Gradle会先执行clean
。通过dependsOn
,我们显式地设置任务之间的依赖关系。这样做的好处是,我们无需手动管理任务的执行顺序,Gradle会根据依赖关系自动安排。
代码解析:
接着我将对上述代码逐句进行一个详细解读,希望能够帮助到同学们,能以最快的速度对其知识点掌握于心,这也是我写此文的初衷,授人以鱼不如授人以渔,只有将其原理摸透,日后应对场景使用,才能得心应手,如鱼得水。所以如果有基础的同学,可以略过如下代码解析,针对没基础的同学,还是需要加强对代码的逻辑与实现,方便日后的你能更深入理解它并常规使用不受限制。
这段 Gradle 构建脚本定义了两个任务:clean
和 build
,并展示了如何使用 dependsOn
来设置任务之间的依赖关系。以下是代码的详细解析:
解析代码
-
clean
任务:task clean { ... }
:定义了一个名为clean
的任务。这个任务的作用通常是清理构建过程中产生的临时文件、编译结果等,确保每次构建从干净的环境开始。doLast { println 'Cleaning project...' }
:该任务执行时,会在最后执行一个操作,打印Cleaning project...
,表示该任务正在清理项目。
-
build
任务:task build { ... }
:定义了一个名为build
的任务。build
任务通常用于编译项目、打包文件等。dependsOn clean
:这一行表示build
任务依赖于clean
任务。也就是说,当你运行build
任务时,Gradle 会先执行clean
任务,然后再执行build
任务。doLast { println 'Building project...' }
:当build
任务执行时,在最后会打印Building project...
,表示正在构建项目。
执行过程
- 当你在命令行中运行
gradle build
时,Gradle 会首先执行clean
任务(因为build
任务依赖于clean
),然后在clean
完成后执行build
任务。执行过程如下:- Gradle 执行
clean
任务,打印:Cleaning project...
- Gradle 执行
build
任务,打印:Building project...
- Gradle 执行
使用示例:
- 在项目的
build.gradle
文件中添加这段代码。 - 运行以下命令:
gradle build
输出会是:
Cleaning project...
Building project...
依赖关系
dependsOn
是 Gradle 中用于设置任务依赖关系的方法。它确保在执行一个任务之前,必须先执行依赖的任务。- 在这个例子中,
build
任务依赖clean
任务,所以每次运行build
任务时,clean
任务会先被执行。这通常用于在构建之前清理掉之前的构建产物,确保构建环境是干净的。
扩展
- 如果你希望任务执行顺序更加复杂,可以使用多个任务依赖。例如,
build
任务可以依赖clean
和其他任务。也可以在任务中添加更多的操作,如编译代码、生成文档等。 - 如果你希望某些操作在任务开始时执行,可以使用
doFirst
,这样就可以在任务开始之前定义需要执行的操作。
总结:
这段代码展示了如何定义两个简单的 Gradle 任务,并使用 dependsOn
来设置任务间的依赖关系,使得 build
任务在执行之前首先执行 clean
任务。这种方式有助于确保在每次构建之前,项目的构建环境是干净的。
📅 定时任务与延迟执行:让任务按需触发 ⏰
在某些情况下,任务的执行可能并不需要立刻发生。比如你可能希望某些任务只有在某些特定条件满足时才执行。这种情况下,Gradle允许你进行延迟执行或者定时任务的设置。例如,某些任务可以在前一个任务完成后才开始,或者你可以根据文件的变化来决定是否执行任务。
看看下面的代码示例:
task delayTask {
doLast {
println 'This task is delayed.'
}
}
task immediateTask {
doLast {
println 'This task runs immediately.'
}
}
delayTask.mustRunAfter immediateTask
在这段代码中,我们定义了两个任务,delayTask
和immediateTask
。通过mustRunAfter
,我们设置delayTask
在immediateTask
之后执行。这样,当你运行任务时,Gradle会确保先执行immediateTask
,然后才执行delayTask
。这种控制顺序的方式非常方便,特别是当任务之间有先后顺序要求时。
代码解析:
接着我将对上述代码逐句进行一个详细解读,希望能够帮助到同学们,能以最快的速度对其知识点掌握于心,这也是我写此文的初衷,授人以鱼不如授人以渔,只有将其原理摸透,日后应对场景使用,才能得心应手,如鱼得水。所以如果有基础的同学,可以略过如下代码解析,针对没基础的同学,还是需要加强对代码的逻辑与实现,方便日后的你能更深入理解它并常规使用不受限制。
这段 Gradle 构建脚本展示了如何使用 mustRunAfter
来设置任务之间的顺序,确保某个任务在另一个任务之后执行。以下是代码的详细解析:
-
delayTask
任务:task delayTask { ... }
:定义了一个名为delayTask
的任务。在任务的doLast
块中,输出This task is delayed.
,表示该任务的执行逻辑是打印一条延迟信息。
-
immediateTask
任务:task immediateTask { ... }
:定义了一个名为immediateTask
的任务。在doLast
块中,输出This task runs immediately.
,表示该任务的执行逻辑是打印立即执行的信息。
-
设置任务执行顺序:
delayTask.mustRunAfter immediateTask
:这行代码确保delayTask
必须在immediateTask
完成后执行。mustRunAfter
表示即使任务之间没有直接的依赖关系,Gradle 也会确保delayTask
在immediateTask
之后执行。如果你尝试在没有执行immediateTask
的情况下直接执行delayTask
,Gradle 会自动调整执行顺序。
执行过程:
-
mustRunAfter
的作用:它指定了任务之间的顺序关系,但是不会强制要求immediateTask
必须先运行。如果你运行gradle delayTask
或gradle immediateTask delayTask
,Gradle 会确保delayTask
在immediateTask
之后执行,即使delayTask
本身没有显式的依赖关系。 -
示例执行:
- 运行
gradle immediateTask delayTask
:- Gradle 会先执行
immediateTask
,打印This task runs immediately.
。 - 然后执行
delayTask
,打印This task is delayed.
。
- Gradle 会先执行
- 运行
使用示例:
假设你在 build.gradle
中有上述任务定义,然后执行以下命令:
gradle delayTask
如果你直接运行 delayTask
,Gradle 会发现 delayTask
必须在 immediateTask
后执行,因此会自动调整执行顺序,先执行 immediateTask
再执行 delayTask
。
运行结果:
This task runs immediately.
This task is delayed.
任务依赖与执行顺序:
mustRunAfter
并不会像dependsOn
一样强制执行任务,它只是确保任务执行顺序。如果任务之间没有直接的依赖关系,mustRunAfter
只是调整执行顺序。- 如果你希望某个任务在另一个任务之后执行,且两个任务之间有执行顺序上的要求,使用
mustRunAfter
是一个很好的选择。
扩展:
- 如果你希望强制一个任务在另一个任务之前执行,可以使用
mustRunBefore
,这样可以确保前一个任务在后一个任务之前执行。 - 可以结合
dependsOn
和mustRunAfter
来实现复杂的任务调度。
总结:
这段代码展示了如何通过 mustRunAfter
来设置任务之间的执行顺序,确保 delayTask
在 immediateTask
执行之后运行。mustRunAfter
使得 Gradle 能够灵活地调整任务的执行顺序,而无需直接建立依赖关系。
🔄 任务的动态创建:动态生成任务 ✨
Gradle不仅仅是一个执行静态任务的工具,它还非常灵活,允许你在构建过程中动态创建任务。这种功能在某些复杂的构建场景中非常有用,比如说你希望根据不同的情况生成不同的任务,或者你想为每个模块创建特定的任务。
看这个例子:
task createTasks {
doLast {
(1..3).each { taskNumber ->
task "task$taskNumber" {
doLast {
println "This is task number $taskNumber"
}
}
}
}
}
在这个示例中,我们通过一个任务createTasks
动态创建了三个新的任务(task1
,task2
,task3
)。通过这种方式,你可以根据需要灵活地创建任务,而不需要手动在构建脚本中为每个任务单独编写代码。这种动态任务创建的能力可以让你的构建脚本更加简洁和灵活。
代码解析:
接着我将对上述代码逐句进行一个详细解读,希望能够帮助到同学们,能以最快的速度对其知识点掌握于心,这也是我写此文的初衷,授人以鱼不如授人以渔,只有将其原理摸透,日后应对场景使用,才能得心应手,如鱼得水。所以如果有基础的同学,可以略过如下代码解析,针对没基础的同学,还是需要加强对代码的逻辑与实现,方便日后的你能更深入理解它并常规使用不受限制。
这段 Gradle 构建脚本定义了一个名为 createTasks
的任务,并在其中动态地创建了三个新的任务。每个新任务的名称和执行逻辑都根据 taskNumber
动态生成。以下是代码的详细解析:
-
createTasks
任务:task createTasks { ... }
:定义了一个名为createTasks
的任务。当执行createTasks
时,它会在执行过程中动态创建三个任务。doLast { ... }
:在createTasks
任务执行时,doLast
块中的代码会被执行。这里的操作是在createTasks
任务执行完成后,动态创建任务。
-
动态任务创建:
(1..3).each { taskNumber -> ... }
:这个代码段使用了 Groovy 的each
方法来遍历从 1 到 3 的数字范围。在每次迭代中,taskNumber
代表当前的数字(1, 2, 或 3)。task "task$taskNumber" { ... }
:在每次迭代中,task "task$taskNumber"
动态创建了一个任务,任务名称为task1
、task2
或task3
,具体取决于taskNumber
的值。taskNumber
通过字符串插值$taskNumber
传递到任务名称中。
-
每个任务的执行逻辑:
doLast { println "This is task number $taskNumber" }
:每个动态创建的任务都有自己的执行逻辑,当执行某个任务时,它会打印出 “This is task number X”(其中 X 是任务编号)。
执行过程
-
运行
createTasks
任务:- 当你运行
gradle createTasks
时,createTasks
任务会执行并且在执行过程中创建三个新的任务:task1
、task2
和task3
。这些任务会根据编号打印不同的信息。
- 当你运行
-
执行顺序:
- 由于任务是动态创建的,它们并不会立即执行。你需要明确运行这些任务,例如运行
gradle task1
或gradle task2
来执行相应的任务。
- 由于任务是动态创建的,它们并不会立即执行。你需要明确运行这些任务,例如运行
使用示例:
假设你在 build.gradle
文件中添加了上述代码并运行:
gradle createTasks
这将创建三个新任务(task1
、task2
和 task3
),但不会自动执行这些任务。要执行这些任务,可以使用:
gradle task1
输出将是:
This is task number 1
同样,运行 gradle task2
将输出:
This is task number 2
总结:
这段代码展示了如何通过 createTasks
动态地生成多个任务,并为每个任务指定不同的执行逻辑。这种方式非常适合在某些情况下动态创建任务,避免重复编写类似的任务代码,提高了构建脚本的灵活性和可维护性。
扩展:
- 你可以根据具体的需求动态生成任务,甚至根据项目的不同配置或条件生成不同的任务。
- 这种方式适用于批量创建类似的任务,减少了冗余代码的编写。
🧠 任务的输入输出:追踪任务的状态 🎯
在Gradle中,任务不仅仅是执行某些操作,它还能追踪任务的输入和输出。通过输入输出,Gradle能够判断任务是否需要重新执行,从而优化构建过程。举个例子,如果某个文件没有发生变化,Gradle就会跳过相关的任务,不再重复执行。这大大提高了构建的效率。
例如,假设你有一个复制文件的任务:
task copyFiles {
inputs.file 'src/file1.txt'
outputs.file 'build/file1.txt'
doLast {
copy {
from 'src/file1.txt'
into 'build'
}
}
}
在这段代码中,copyFiles
任务的输入是src/file1.txt
,输出是build/file1.txt
。Gradle会在每次构建时检查这些文件的变化,如果src/file1.txt
没有变化,Gradle就会跳过copyFiles
任务,从而节省时间。这种机制非常适合需要频繁构建的项目,能够有效提高构建速度。
代码解析:
接着我将对上述代码逐句进行一个详细解读,希望能够帮助到同学们,能以最快的速度对其知识点掌握于心,这也是我写此文的初衷,授人以鱼不如授人以渔,只有将其原理摸透,日后应对场景使用,才能得心应手,如鱼得水。所以如果有基础的同学,可以略过如下代码解析,针对没基础的同学,还是需要加强对代码的逻辑与实现,方便日后的你能更深入理解它并常规使用不受限制。
这段 Gradle 构建脚本定义了一个名为 copyFiles
的任务,该任务将 src/file1.txt
文件复制到 build
目录下。它展示了如何使用 inputs
和 outputs
属性来声明任务的输入和输出文件,以及如何在任务中执行实际的文件操作。以下是代码的详细解析:
-
copyFiles
任务定义:task copyFiles { ... }
:定义了一个名为copyFiles
的任务。该任务会将指定的文件从源位置复制到目标位置。
-
任务输入与输出:
inputs.file 'src/file1.txt'
:声明copyFiles
任务的输入文件是src/file1.txt
。这意味着在执行任务时,Gradle 会将该文件作为输入,进行文件的复制操作。outputs.file 'build/file1.txt'
:声明copyFiles
任务的输出文件是build/file1.txt
。Gradle 会监控该文件作为任务的输出,并在文件没有变化的情况下跳过任务执行(实现增量构建)。如果该文件已存在且内容没有更改,Gradle 会根据该输出声明判断是否需要重新执行该任务。
-
任务执行逻辑:
doLast { ... }
:这是任务的执行逻辑部分,任务完成时会执行doLast
块中的代码。copy { ... }
:copy
是一个 Gradle 内置的任务类型,用于执行文件复制操作。from
表示源文件,into
表示目标目录。在这里,from 'src/file1.txt'
指定了源文件路径,而into 'build'
指定了目标目录。任务执行时会将src/file1.txt
文件复制到build
目录下,并命名为file1.txt
。
执行过程
-
运行
copyFiles
任务:- 当你运行
gradle copyFiles
时,Gradle 会首先检查inputs
和outputs
。如果src/file1.txt
发生了变化,且build/file1.txt
需要更新,Gradle 会执行copy
操作,将源文件复制到目标目录。
- 当你运行
-
增量构建:
- 通过声明输入和输出文件,Gradle 可以智能地判断任务是否需要重新执行。如果文件没有发生变化,Gradle 会跳过该任务,从而节省时间,提高构建效率。
- 如果
src/file1.txt
没有发生变化,且build/file1.txt
已存在且没有变化,Gradle 会跳过该任务。
使用示例:
假设你在 build.gradle
文件中添加了上述代码,并且目录结构如下:
project/
├── build.gradle
└── src/
└── file1.txt
执行以下命令:
gradle copyFiles
如果 src/file1.txt
存在,Gradle 会将其复制到 build
目录,创建或覆盖 build/file1.txt
文件。
总结:
这段代码演示了如何使用 Gradle 进行文件复制操作,并结合 inputs
和 outputs
属性进行增量构建。通过声明输入输出文件,Gradle 可以根据文件的变化情况决定是否重新执行任务,从而提高构建效率。
扩展:
- 你可以通过使用不同的
from
和into
路径来复制多个文件或目录。 - 可以使用
copy
的其他选项,比如include
和exclude
,来过滤文件的复制。 - 对于更复杂的文件复制需求,Gradle 还提供了更灵活的配置,如指定文件的权限、修改文件内容等。
💡 Task的自定义:创建你自己的任务 👨💻
虽然Gradle已经内置了许多常用的任务,但有时候你可能需要根据自己的需求来定义自定义任务。幸运的是,Gradle允许你非常容易地创建自定义任务。这不仅可以帮助你实现复杂的构建逻辑,还能使构建脚本更具可读性。
看一个简单的例子,如何自定义一个任务:
class MyCustomTask extends DefaultTask {
@TaskAction
def myAction() {
println 'Running custom task!'
}
}
task customTask(type: MyCustomTask)
在这个例子中,我们创建了一个自定义任务MyCustomTask
,并在其中定义了一个名为myAction
的方法。通过@TaskAction
注解,Gradle会知道这个方法是任务的执行内容。当你运行customTask
时,myAction
方法就会被调用。
代码解析:
接着我将对上述代码逐句进行一个详细解读,希望能够帮助到同学们,能以最快的速度对其知识点掌握于心,这也是我写此文的初衷,授人以鱼不如授人以渔,只有将其原理摸透,日后应对场景使用,才能得心应手,如鱼得水。所以如果有基础的同学,可以略过如下代码解析,针对没基础的同学,还是需要加强对代码的逻辑与实现,方便日后的你能更深入理解它并常规使用不受限制。
这段 Gradle 构建脚本定义了一个自定义任务 customTask
,它通过继承 DefaultTask
类创建了一个名为 MyCustomTask
的自定义任务类,并在其中实现了任务的执行逻辑。以下是代码的详细解析:
-
自定义任务类:
class MyCustomTask extends DefaultTask { ... }
:定义了一个名为MyCustomTask
的自定义任务类。这个类继承自DefaultTask
,使得它成为一个合法的 Gradle 任务。DefaultTask
是 Gradle 提供的一个任务基类,它已经实现了任务的基本行为,允许你直接扩展并添加自定义逻辑。
-
任务操作方法:
@TaskAction
:这是 Gradle 的一个注解,用于标记方法作为任务的执行操作。带有@TaskAction
注解的方法会在任务执行时被调用,执行任务的主要逻辑。在本例中,myAction()
方法就是标记为任务的执行操作,它会在customTask
任务执行时被调用。def myAction()
:这是自定义任务的执行逻辑,执行时会打印Running custom task!
,表示任务正在运行。
-
创建任务实例:
task customTask(type: MyCustomTask)
:这行代码创建了一个customTask
任务,它的类型是MyCustomTask
。Gradle 会在执行customTask
时调用MyCustomTask
类中的myAction
方法,执行任务的自定义逻辑。
执行过程
-
运行
customTask
任务:- 当你运行
gradle customTask
时,Gradle 会执行customTask
任务,而该任务会调用MyCustomTask
类中的myAction()
方法。方法执行时会打印Running custom task!
。
- 当你运行
-
执行逻辑:
- 在执行
gradle customTask
时,Gradle 会首先实例化MyCustomTask
类,然后查找并执行带有@TaskAction
注解的方法(在这里是myAction()
),最后打印任务的执行信息。
- 在执行
使用示例:
假设你在 build.gradle
文件中添加了上述代码,运行以下命令:
gradle customTask
输出将会是:
Running custom task!
这表示自定义任务成功执行,并输出了任务定义中的打印信息。
总结:
这段代码展示了如何通过继承 DefaultTask
类来创建自定义任务,并通过 @TaskAction
注解的方法定义任务的执行逻辑。自定义任务为 Gradle 构建脚本提供了极大的灵活性,使得用户能够根据实际需要编写任务。
扩展:
- 你可以在
MyCustomTask
类中定义更多的属性和方法,从而让自定义任务执行更复杂的操作,比如修改项目文件、配置构建参数等。 @TaskAction
可以标记多个方法,但每个任务只能执行一个方法作为任务的核心操作。如果有多个操作,通常会将它们放在一个方法中,或者调用其他方法来分离任务逻辑。- 可以通过 Gradle 的
inputs
和outputs
来声明任务的输入输出文件,使得任务支持增量构建,只有在输入文件变化时才执行任务。
🌈 总结:Task,Gradle的真正力量 💥
Gradle的Task是它的核心所在,理解并合理利用Task,可以让你的构建过程更加高效、灵活且强大。无论是简单的任务、复杂的依赖关系,还是动态任务的生成,Gradle都能帮你轻松实现。
通过本文的学习,我们不仅了解了Task的基础用法,还深入探讨了任务的生命周期、依赖关系、输入输出、延迟执行等进阶特性。掌握了这些,你将能够用Gradle构建出更符合自己需求的自动化构建流程,提升开发效率,轻松应对各种复杂的构建任务!
🧧福利赠与你🧧
无论你是计算机专业的学生,还是对编程有兴趣的小伙伴,都建议直接毫无顾忌的学习此专栏「滚雪球学SpringBoot」,bug菌郑重承诺,凡是学习此专栏的同学,均能获取到所需的知识和技能,全网最快速入门SpringBoot,就像滚雪球一样,越滚越大, 无边无际,指数级提升。
最后,如果这篇文章对你有所帮助,帮忙给作者来个一键三连,关注、点赞、收藏,您的支持就是我坚持写作最大的动力。
同时欢迎大家关注公众号:「猿圈奇妙屋」 ,以便学习更多同类型的技术文章,免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板、技术文章Markdown文档等海量资料。
✨️ Who am I?
我是bug菌,CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家,C站博客之星Top30,华为云2023年度十佳博主,掘金多年度人气作者Top40,掘金等各大社区平台签约作者,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者;全网粉丝合计 30w+;更多精彩福利点击这里;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试真题、4000G PDF电子书籍、简历模板等海量资料,你想要的我都有,关键是你不来拿。
-End-
- 点赞
- 收藏
- 关注作者
评论(0)