当前位置:网站首页>Gradle 学习 ----Gradle 进阶说明
Gradle 学习 ----Gradle 进阶说明
2022-07-24 21:45:00 【你若不离不弃,我必生死相依】
3 、Gradle进阶说明
为了让大家快速的入门gradle,本章将从整体构建脚本的角度介绍:
什么是setting文件,它有什么作用;
说明什么是build文件,它又有什么作用
我们可以创建多少个build
project和task,他们有什么作用;又是什么关系,如何配置
项目的生命周期
项目发布
使用Gradle创建SpringBoot项目等
3. 1 、项目的生命周期
Gradle项目的生命周期分为三大阶段:Initialization->Configuration->Execution.每个阶段都有自己的职责,具体如下图所示:
Initialization阶段 主要目的是初始化构建,它又分为两个子过程,一个是执行InitScript,另一个是执行SettingScript。
init.gradle文件会在每个项目build之前被调用,用于做一些初始化的操作,它主要有如下作用:
Setting Script 则更重要,它初始化了一次构建所参与的所有模块。
Configuration阶段 :这个阶段开始加载项目中所有模块的BuildScript。所谓"加载"就是执行build.gradle中的语句,根据脚本代码创建对应的task,最终根据所有task生成由 Task组成的有向无环图 (DirectedAcyclicGraphs),如下:
从而构成如下 有向无环树 :
Execution阶段 :这个阶段会根据上个阶段构建好的有向无环图,按着顺序执行Task【Action动作】。
3. 2 、settings文件
首先对settings文件的几点说明:
1 、作用:主要是在项目初始化阶段确定一下引入哪些工程需要加入到项目构建中,为构建项目工程树做准备。
2 、工程树:gradle中有工程树的概念,类似于maven中的project与module。
配置内部的仓库信息(如公司的maven仓库信息); 配置一些全局属性; 配置用户名及密码信息(如公司仓库的用户名和密码信息)。
3 、内容:里面主要定义了当前gradle项目及子project的项目名称
4 、位置:必须放在根工程目录下。
5 、名字:为settings.gradle文件,不能发生变化
6 、对应实例:与org.gradle.api.initialization.Settings实例是一一对应的关系。每个项目只有一个settings文件。
7 、关注:作为开发者我们只需要关注该文件中的include方法即可。使用相对路径【:】引入子工程。
8 .一个子工程只有在setting文件中配置了才会被gradle识别,这样在构建的时候才会被包含进去。案例如下所示:
//根工程项目名
rootProject.name = 'root'
//包含的子工程名称
include 'subject01'
include 'subject02'
include 'subject03'
//包含的子工程下的子工程名称
include 'subject01:subproject011'
include 'subject01:subproject012'
项目名称中":“代表项目的分隔符,类似路径中的”/“.如果以”:"开头则表示相对于rootproject。然后Gradle会为每个带有build.gradle脚本文件的工程构建一个与之对应的Project对象。
3. 3 、Task
项目实质上是Task对象的集合。一个Task表示一个逻辑上较为独立的执行过程,比如编译Java源代码,拷贝文件,打包Jar文件,甚至可以是执行一个系统命令。另外,一个Task可以读取和设置Project的Property以完成特定的操作。
3. 3. 1 、任务入门
可参考官方文档:https://docs.gradle.org/current/userguide/tutorial_using_tasks.html
让我们来先看一个例子:
task A {
println "root taskA"
doFirst(){
println "root taskA doFirst"
}
doLast(){
println "root taskA doLast"
}
}
在文件所在的目录执行命令:gradle A。
提示 1 :task的配置段是在配置阶段完成
提示 2 :task的doFirst、doLast方法是执行阶段完成,并且doFirst在doLast执行之前执行。
提示 3 : 区分任务的配置段和任务的行为,任务的配置段在配置阶段执行,任务的行为在执行阶段执行
3. 3. 2 、任务的行为
案例如下:doFirst、doLast两个方法可以在任务内部定义,也可以在任务外部定义
def map=new HashMap<String,Object>();
//action属性可以设置为闭包,设置task自身的行为
map.put("action",{println "taskD.."})
task(map,"a"){
description 'taskA description '
group "atguigu"
//在task内部定义doFirst、doLast行为
doFirst {
def name = 'doFirst..'
println name
}
doLast {
def name = 'doLast..'
println name
}
}
//在task外部定义doFirst、doLast行为
a.doFirst {
println it.description
}
a.doLast {
println it.group
}
测试:gradlea ,输出如下所示:
底层原理分析 :无论是定义任务自身的action,还是添加的doLast、doFirst方法,其实底层都被放入到一个Action的List中了,最初这个actionList是空的,当我们设置了action【任务自身的行为】,它先将action添加到列表中,此时列表中只有一个action,后续执行doFirst的时候doFirst在action前面添加,执行doLast的时候doLast在action后面添加。doFirst永远添加在actionsList的第一位,保证添加的Action在现有的actionList元素的最前面;doLast永远都是在actionList末尾添加,保证其添加的Action在现有的actionList元素的最后面。一个往前面添加,一个往后面添加,最后这个actionList就按顺序形成了doFirst、doSelf、doLast三部分的Actions,就达到doFirst、doSelf、doLast三部分的Actions顺序执行的目的。
提示 1 :其中<<代表doLast,在gradle 5 .x版本之后就废弃,不能使用了,如下所示:
task hello << {
println 'Hello world!'
}
3. 3. 3 、任务的依赖方式
Task之间的依赖关系可以在以下几部分设置:
- 参数依赖
- 内部依赖
- 外部依赖
方式一:参数方式依赖
task A {
doLast {
println "TaskA.."
}
}
task 'B' {
doLast {
println "TaskB.."
}
}
//参数方式依赖: dependsOn后面用冒号
task 'C'(dependsOn: ['A', 'B']) {
doLast {
println "TaskC.."
}
}}
方式二:内部依赖
//参数方式依赖
task 'C' {
//内部依赖:dependsOn后面用 = 号
dependsOn= [A,B]
doLast {
println "TaskC.."
}
}
测试:gradleC
方式三:外部依赖
当然:task也支持跨项目依赖
//外部依赖:可变参数,引号可加可不加
C.dependsOn(B,'A')
在subproject 01 工程的build.gradle文件中定义:
task A {
doLast {
println "TaskA.."
}
}
在subproject 02 工程的build.gradle文件中定义:
task B{
dependsOn(":subproject01:A") //依赖根工程下的subject01中的任务A :跨项目依赖。
doLast {
println "TaskB.."
}
}
测试:gradle B,控制台显示如下
拓展 1 :当一个Task依赖多个Task的时候,被依赖的Task之间如果没有依赖关系,那么它们的执行顺序是随机的,并无影响
拓展 2 :重复依赖的任务只会执行一次, 比如:
A->B、C
B->C
任务A 依赖任务 B 和任务 C、任务 B 依赖C 任务。执行任务A 的时候,显然任务C 被重复依赖了,C 只会执行一次。
3. 3. 4 、任务执行
任务执行语法: gradle [taskName…] [–option-name…]。
| 分类 | 解释 |
|---|---|
| 常见的任务(*) | |
| gradlebuild | 构建项目:编译、测试、打包等操作 |
| gradlerun | 运行一个服务,需要application插件支持,并且指定了主启动 类才能运行 |
| gradleclean | 请求当前项目的build目录 |
| gradleinit | 初始化gradle项目使用 |
| gradlewrapper | 生成wrapper文件夹的。 |
| gradlewrapper升级wrapper版本号 | gradlewrapper–gradle-version= 4. 4 |
| gradlewrapper–gradle-version 5. 2. 1 - -distribution-typeall | 关联源码用 |
| 项目报告相关任务 | |
| gradleprojects | 列出所选项目及子项目列表,以层次结构的形式显示 |
| gradletasks: | 列出所选项目【当前project,不包含父、子】的已分配给任务组的那些任务。 |
| gradletasks–all | 列出所选项目的所有任务。 |
| gradletasks–group=“buildsetup” | 列出所选项目中指定分组中的任务。 |
| gradlehelp–tasksomeTask | 显示某个任务的详细信息 |
| gradledependencies | 查看整个项目的依赖信息,以依赖树的方式显示 |
| gradleproperties | 列出所选项目的属性列表 |
| 调试相关选项 | |
| -h,–help | 查看帮助信息 |
| v,–version | 打印Gradle、Groovy、Ant、JVM和操作系统版本信息。 |
| S,–full-stacktrace | 打印出所有异常的完整(非常详细)堆栈跟踪信息。 |
| s,–stacktrace | 打印出用户异常的堆栈跟踪(例如编译错误)。 |
| Dorg.gradle.daemon.debug=true | 调试Gradle守护进程。 |
| Dorg.gradle.debug=true | 调试Gradle客户端(非daemon)进程。 |
| Dorg.gradle.debug.port=(portnumber) | 指定启用调试时要侦听的端口号。默认值为 5005 。 |
| 性能选项:【 备注 :在gradle.properties中指定这些选项中的许多选项,因此不需要命令行标志】 | -build-cache,–no-build-cache:尝试重用先前版本的输出。默认关闭(off)。 |
| -max-workers | 设置Gradle可以使用的woker数。默认值是处理器数。 |
| parallel,–no-parallel | 并行执行项目。有关此选项的限制,请参阅并行项目执行。默认设置为关闭(off) |
| 守护进程选项 | –daemon,–no-daemon |
| 日志选项 | -Dorg.gradle.logging.level=(quiet,warn,lifecycle,info,debug):通过Gradle属性设置日志记录级别。q,–quiet:只能记录错误信息w,–warn:设置日志级别为warni,–info:将日志级别设置为infod,–debug:登录调试模式(包括正常的堆栈跟踪) |
| 其它(*) | - x:-x等价于:–exclude-task: 常见gradle-xtestcleanbuild-rerun-tasks:强制执行任务,忽略up-to-date,常见gradlebuild–rerun-tasks-continue:忽略前面失败的任务,继续执行,而不是在遇到第一个失败时立即停止执行。每个遇到的故障都将在构建结束时报告,常见:gradlebuild–continue。gradleinit–typepom:将maven项目转换为gradle项目(根目录执行)gradle[taskName]:执行自定义任务 |
| … |
更详细请参考官方文档:
拓展:gradle任务名是缩写:任务名支持驼峰式命名风格的任务名缩写,如:connectTask简写为:cT,执行任务gradlecT。
拓展 1 :前面提到的Gradle指令本质:一个个的task[任务],Gradle中所有操作都是基于任务完成的。
拓展 2 :gradle默认各指令之间相互的依赖关系:
相关解释:
3. 3. 5 、任务定义方式
任务定义方式,总体分为两大类:一种是通过Project中的task()方法,另一种是通过tasks对象的create或者register方法。
task( 'A' , { //任务名称,闭包都作为参数
println "taskA..."
} )
task( 'B' ) { //闭包作为最后一个参数可以直接从括号中拿出来
println "taskB..."
}
taskC { //groovy语法支持省略方法括号:上面三种本质是一种
println "taskC..."
}
def map= new HashMap<String,Object>();
map.put( "action" , { println "taskD.."} )//action属性可以设置为闭包
task(map, "D" );
tasks.create( 'E' ) { //使用tasks的create方法
println "taskE.."
}
tasks.register( 'f' ) { // 注:register执行的是延迟创建。也即只有当task被需要使用的时候才会被创建。
println "taskF...."
}
当然:我们也可以在定义任务的同时指定任务的属性,具体属性有:
在定义任务时也可以给任务分配属性 :定义任务的时候可以直接指定任务属性,也可以给已有的任务动态分配属性:
/①.F是任务名,前面通过具名参数给map的属性赋值,以参数方式指定任务的属性信息
task(group: "atguigu",description: "this is task B","F")
//②.H是任务名,定义任务的同时,在内部直接指定属性信息
task("H") {
group("atguigu") description("this is the task H")
}
//③.Y是任务名,给已有的任务 在外部直接指定属性信息
task "y"{}
y.group="atguigu"
clean.group("atguigu") //案例:给已有的clean任务重新指定组信息
可以在 idea 中看到: 上面自定义的那几个任务和 gradle 自带的 clean 任务已经跑到:atguigu 组了。
可以在idea中看到:上面自定义的那几个任务和gradle自带的clean任务已经跑到:atguigu组了。
3. 3. 6 、任务类型
前面我们定义的task都是DefaultTask类型的,如果要完成某些具体的操作完全需要我们自己去编写gradle脚本,势必有些麻烦,那有没有一些现成的任务类型可以使用呢?有的,Gradle官网给出了一些现成的任务类型帮助我们快速完成想要的任务,我们只需要在创建任务的时候,指定当前任务的类型即可,然后即可使用这种类型中的属性和API方法了。
| 常见任务类型 | 该类型任务的作用 |
|---|---|
| Delete | – |
| Copy | 将文件复制到目标目录中。此任务还可以在复制时重命名和筛选文件。 |
| CreateStartScripts | 创建启动脚本 |
| Exec | 执行命令行进程 |
| GenerateMavenPom | 生成Maven模块描述符(POM)文件。 |
| GradleBuild | 执行Gradle构建 |
| Jar | 组装JAR归档文件 |
| JavaCompile | 编译Java源文件 |
| Javadoc | 为Java类生成HTMLAPI文档 |
| PublishToMavenRepository | 将MavenPublication发布到mavenartifactrepostal。 |
| Tar | 组装TAR存档文件 |
| Test | 执行JUnit( 3. 8 .x、 4 .x或 5 .x)或TestNG测试。 |
| Upload | 将Configuration的构件上传到一组存储库。 |
| War | 组装WAR档案。 |
| Zip | 组装ZIP归档文件。默认是压缩ZIP的内容。 |
提示 1 :如果想看更详细的gradle自带Task类型,请参考官方文档:https://docs.gradle.org/current/dsl/index.html
提示 2 :官方文档在给出这些任务类型的时候,同时给出了案例代码,可以点进去上述官网地址中的某个类型中观看
具体使用例如:
tasks.register('myClean', Delete) { delete buildDir
}
在命令行执行gradlemyClean发现就可以将当前project的build目录删除
当然除了gradle自带的task类型,我们也可以自定义task类型,如下所示:
def myTask=task MyDefinitionTask (type: CustomTask) myTask.doFirst(){
println "task 执行之前 执行的 doFirst方法"
}
myTask.doLast(){
println "task 执行之后 执行的 doLast方法"
}
class CustomTask extends DefaultTask {
//@TaskAction表示Task本身要执行的方法@TaskAction
def doSelf(){
println "Task 自身 在执行的in doSelf"
}
}
拓展 4 :自定义Task类型
测试:gradleMyDefinitionTask 控制台输出:
3. 3. 7 、任务的执行顺序
在Gradle中,有三种方式可以指定Task执行顺序:
1 、dependsOn强依赖方式
2 、通过Task输入输出
3 、通过API指定执行顺序
详细请参考官网:https://docs.gradle.org/current/dsl/org.gradle.api.Task.html
tasks.register('myClean',Delete){
deletebuildDir
}
defmyTask=taskMyDefinitionTask(type:CustomTask)
myTask.doFirst(){
println"task执行之前执行的doFirst方法"
}
myTask.doLast(){
println"task执行之后执行的doLast方法"
}
classCustomTaskextendsDefaultTask{
//@TaskAction表示Task本身要执行的方法
@TaskAction
defdoSelf(){
println"Task自身在执行的indoSelf"
}
}
一旦注册了任务,就可以通过 API 访问它们。例如,您可以使用它在运行时动态地向任务添加依赖项。Ant 不允许这样的事情发生。

3. 3. 8 、动态分配任务
gradle的强大功能不仅仅用于定义任务的功能。例如,可以使用它在循环中注册同一类型的多个任务一旦注册了任务,就可以通过API访问它们。例如,您可以使用它在运行时动态地向任务添加依赖项。Ant不允许这样
的事情发生。
构建 4 个任务,但是任务 0 必须依赖于任务 2 和 3 ,那么代表任务 2 和 3 需要在任务 0 之前优先加载。具体测试如下:
3. 3. 9 、任务的关闭与开启
每个任务都有一个enabled默认为的标志true。将其设置为false阻止执行任何任务动作。禁用的任务将标记为“跳过”。
task disableMe {
doLast {
println 'This task is Executing...'
}
enabled(true)//直接设置任务开启,默认值为true
}
//disableMe.enabled = false //设置关闭任务
3. 3. 10 、任务的超时
每个任务都有一个timeout可用于限制其执行时间的属性。当任务达到超时时,其任务执行线程将被中断。该任务将被标记为失败。终结器任务仍将运行。如果–continue使用,其他任务可以在此之后继续运行。不响应中断的任务无法超时。Gradle的所有内置任务均会及时响应超时
4.times{counter->
tasks.register("task$counter"){
doLast{
println"I'mtasknumber$counter"
}
}
}
4 .times{counter->
tasks.register("task$counter"){
doLast{
println"I'mtasknumber$counter"
}
}
}
tasks.named('task 0 '){dependsOn('task 2 ','task 3 ')}
taskdisableMe {
doLast {
println 'ThistaskisExecuting...'
}
enabled( true )//直接设置任务开启,默认值为true
}
//disableMe.enabled=false// 设置关闭任务
在控制台使用 :gradleab 测试会发现执行a的时候,由于a执行超时,抛出异常,所以没有继续往下执行【b也没执行】。然后在控制台使用: gradleab–continue, 测试会发现a虽然执行失败,但是b还是执行了。
3. 3. 11 、任务的查找
常用的任务查找方法有:
task atguigu {
doLast {
println "让天下没有难学的技术:尚硅谷"
}
}
//根据任务名查找
tasks.findByName("atguigu").doFirst({println "尚硅谷校区1:北京 "})
tasks.getByName("atguigu").doFirst({println "尚硅谷校区2:深圳 "})
//根据任务路径查找【相对路径】
tasks.findByPath(":atguigu").doFirst({println "尚硅谷校区3:上海 "}) tasks.getByPath(":atguigu").doFirst({println "尚硅谷校区4:武汉 "})
执行task: gradle atguigu,输出结果如下所示:
尚硅谷校区4:武汉....
尚硅谷校区3:上海....
尚硅谷校区2:深圳....
尚硅谷校区1:北京....
让天下没有难学的技术:尚硅谷
3. 3. 12 、任务的规则
当我们执行、依赖一个不存在的任务时,Gradle会执行失败,报错误信息。那我们能否对其进行改进,当执行一个不存在的任务时,不是报错而是打印提示信息呢?
测试:使用gradleabchello进行测试,此时当abc任务不存在时,也不会报异常【不中断执行】而是提示自定义的规则信息,继续执行hello任务。此外,它还可以根据不同的规则动态创建需要的任务等情况。
taska() {
doLast {
Thread. sleep ( 1000 )
println "当前任务a执行了"
}
timeout=Duration.ofMillis( 500 )
}
taskb() {
doLast {
println "当前任务b执行了"
}
}
taskatguigu {
doLast {
println "让天下没有难学的技术:尚硅谷"
}
}
// 根据任务名查找
tasks.findByName( "atguigu" ).doFirst( { println "尚硅谷校区 1 :北京...."} )
tasks.getByName( "atguigu" ).doFirst( { println "尚硅谷校区 2 :深圳...."} )
// 根据任务路径查找【相对路径】
tasks.findByPath( ":atguigu" ).doFirst( { println "尚硅谷校区 3 :上海...."} )
tasks.getByPath( ":atguigu" ).doFirst( { println "尚硅谷校区 4 :武汉...."} )
尚硅谷校区 4 :武汉....
尚硅谷校区 3 :上海....
尚硅谷校区 2 :深圳....
尚硅谷校区 1 :北京....
让天下没有难学的技术:尚硅谷
taskhello{
doLast{
println'hello尚硅谷的粉丝们'
}
}
tasks.addRule("对该规则的一个描述,便于调试、查看等"){
StringtaskName->task(taskName){
doLast{
println"该${taskName}任务不存在,请查证后再执行"
}
}
}
测试: 使用 gradle abc hello 进行测试,此时当 abc 任务不存在时,也不会报异常【不中断执行】而是提示自定义的规则信息,继续执行 hello 任务。此外,它还可以根据不同的规则动态创建需要的任务等情况。
3. 3. 13 、任务的onlyIf断言
断言就是一个条件表达式。Task有一个onlyIf方法。它接受一个闭包作为参数,如果该闭包返回true则该任务执行,否则跳过。这有很多用途,比如控制程序哪些情况下打什么包,什么时候执行单元测试,什么情况下执行单元测试的时候不执行网络测试等。具体案例如下所示:
task hello {
doLast {
println 'hello 尚硅谷的粉丝们'
}
}
hello.onlyIf { !project.hasProperty('fensi') }
测试:通过-P为Project添加fensi属性
gradle hello -Pfensi
3. 3. 14 、默认任务
Gradle允许您定义一个或多个在没有指定其他任务时执行的默认任务。
代码如下所示:
defaultTasks 'myClean', 'myRun' tasks.register('myClean'){
doLast {
println 'Default Cleaning!'
}
}
tasks.register('myRun') { doLast {
println 'Default Running!'
}
}
tasks.register('other') { doLast {
println "I'm not a default task!"
}
}
测试及结果如下:
> gradle -q Default Cleaning!
Default Running!
3. 4 、Gradle中的文件操作
几种常见的文件操作方式:
本地文件
文件集合
文件树
文件拷贝
归档文件
各种文件操作类型的详细介绍如下所示:
3. 4. 1 、本地文件
使用 Project.file(java.lang.Object)方法,通过指定文件的相对路径或绝对路径来对文件的操作,其中相对路径为相对当前project[根project 或者子project]的目录。其实使用 Project.file(java.lang.Object)方法创建的 File 对象就是 Java 中的 File 对象,我们可以使用它就像在 Java 中使用一样。示例代码如下:
//使用相对路径
File configFile = file('src/conf.xml')
configFile.createNewFile();
// 使用绝对路径
configFile = file('D:\\conf.xml')
println(configFile.createNewFile())
// 使用一个文件对象
configFile = new File('src/config.xml')
println(configFile.exists())
3. 4. 2 、文件集合
文件集合就是一组文件的列表,在Gradle中,文件集合FileCollection 接口表示。我们可以使用Project.files(java.lang.Object[])方法来获得一个文件集合对象,如下代码创建一个FileCollection实例:
def collection = files('src/test1.txt',new File('src/test2.txt'),['src/test3.txt', 'src/test4.txt']) collection.forEach(){File it ->
it.createNewFile() //创建该文件
println it.name //输出文件名
}
Set set1 = collection.files // 把文件集合转换为java中的Set类型
Set set2 = collection as Set
List list = collection as List// 把文件集合转换为java中的List类型
for (item in list) {
println item.name }
def union = collection + files('src/test5.txt') // 添加或者删除一个集合
def minus = collection - files('src/test3.txt') union.forEach(){File it ->
println it.name
}
对于文件集合我们可以遍历它;也可以把它转换成java类型;同时还能使用+来添加一个集合,或使用-来删除集合。
3. 4. 3 、文件树
文件树是有层级结构的文件集合 ,一个文件树它可以代表一个目录结构或一ZIP压缩包中的内容结构。文件树是从文件集合继承过来的, 所以文件树具有文件集合所有的功能 。我们可以使用Project.fileTree(java.util.Map)方法来创建文件树对象,还可以使用过虑条件来包含或排除相关文件。示例代码如下:
tree=fileTree( 'src/main' ).include( '/.java'* )//第一种方式:使用路径创建文件树对象,同时指定包含的文件
//第二种方式:通过闭包创建文件树:
tree=fileTree( 'src/main' ) {
include '/.java'
}*
tree=fileTree( dir : 'src/main' , include : '/.java'* )//第三种方式:通过路径和闭包创建文件树:具名参数给map传值
tree=fileTree( dir : 'src/main' , includes :[ '/.java'* , '/.xml'* , '/.txt'* ], exclude : '/test/' )
tree.each { Filefile - > //遍历文件树的所有文件
printlnfile
printlnfile.name
}
3. 4. 4 、文件拷贝
我们可以使用Copy任务来拷贝文件,通过它可以过虑指定拷贝内容,还能对文件进行重命名操作等。Copy任务必须指
定一组需要拷贝的文件和拷贝到的目录,这里使用CopySpec.from(java.lang.Object[])方法指定原文件;使用
CopySpec.into(java.lang.Object)方法指定目标目录。示例代码如下:
task copyTask(type: Copy) {
from 'src/main/resources' into 'build/config' }
from()方法接受的参数和文件集合时files()一样。当参数为一个目录时,该目录下所有的文件都会被拷贝到指定目录下(目
录自身不会被拷贝);当参数为一个文件时,该文件会被拷贝到指定目录;如果参数指定的文件不存在,就会被忽略;
当参数为一个Zip压缩文件,该压缩文件的内容会被拷贝到指定目录。into()方法接受的参数与本地文件时file()一样。示例代码如下
taskcopyTask(type:Copy){
//拷贝src/main/webapp目录下所有的文件
from'src/main/webapp'
//拷贝单独的一个文件
from'src/staging/index.html'
//从Zip压缩文件中拷贝内容
fromzipTree('src/main/assets.zip')
//拷贝到的目标目录
into'build/explodedWar'
}
在拷贝文件的时候还可以添加过虑条件来指定包含或排除的文件,示例如下:
task copyTaskWithPatterns(type: Copy) {
from 'src/main/webapp' into 'build/explodedWar' include '**/*.html' include '**/*.jsp' exclude { details -> details.file.name.endsWith('.html') }
}
在拷贝文件的时候还可以对文件进行重命名操作,示例如下:
task rename(type: Copy) {
from 'src/main/webapp' into 'build/explodedWar' // 使用一个闭包方式重命名文件
rename { String fileName ->
fileName.replace('-staging-', '')
}
}
在上面的例子中我们都是使用Copy任务来完成拷贝功能的,那么有没有另外一种方式呢?答案是肯定的,那就是Project.copy(org.gradle.api.Action)方法。下面示例展示了copy()方法的使用方式:
task rename(type: Copy) {
from 'src/main/webapp' into 'build/explodedWar' // 使用一个闭包方式重命名文件
rename { String fileName ->
fileName.replace('-staging-', '')
}
}
或者使用project对象的copy方法:
copy {
//相对路径或者绝对路径
from file('src/main/resources/ddd.txt') //file也可以换成new File()
into this.buildDir.absolutePath
}
执行gradlebuild指令即可。去build目录的本地磁盘查看,就能看到。
3. 4. 5 、归档文件
通常一个项目会有很多的Jar包,我们希望把项目打包成一个WAR,ZIP或TAR包进行发布,这时我们就可以使用Zip,Tar,Jar,War和Ear任务来实现,不过它们的用法都一样,所以在这里我只介绍Zip任务的示例。
首先,创建一个Zip压缩文件,并指定压缩文件名称,如下代码所示:
apply plugin: 'java' version=1.0
task myZip(type: Zip) {
from 'src/main‘ into ‘build’ //保存到build目录中
baseName = 'myGame' }
println myZip.archiveName
执行命令gradle-qmyZip,输出结果为:
> gradle -q myZip myGame-1.0.zip
最后,我们可以使用Project.zipTree(java.lang.Object)和Project.tarTree(java.lang.Object)方法来创建访问Zip压缩包的文
件树对象,示例代码如下:
// 使用zipTree FileTree zip = zipTree('someFile.zip')
// 使用tarTree FileTree tar = tarTree('someFile.tar')
在这里,我们介绍了Gradle对本地文件、文件集合、文件树、文件拷贝和归档文件的操作方式。更详细的请参考官方文档:https://docs.gradle.org/current/userguide/working_with_files.html
3. 5 、Dependencies
3. 5. 1 、依赖的方式
Gradle中的依赖分别为直接依赖,项目依赖,本地jar依赖。
案例如下:
dependencies {
//①.依赖当前项目下的某个模块[子工程]
implementation project(':subject01')
//②.直接依赖本地的某个jar文件
implementation files('libs/foo.jar', 'libs/bar.jar')
//②.配置某文件夹作为依赖项
implementation fileTree(dir: 'libs', include: ['*.jar'])
//③.直接依赖
implementation 'org.apache.logging.log4j:log4j:2.17.2' }
直接依赖 :在项目中直接导入的依赖,就是直接依赖
implementation 'org.apache.logging.log4j:log4j:2.17.2'
上面是简写法,完整版写法如下:
implementation group: ‘org.apache.logging.log4j’, name: ‘log4j’, version: ‘2.17.2’
group/name/version 共同定位一个远程仓库,version 最好写一个固定的版本号,以防构建出问题,implementation 类似
maven 中的依赖的 scope,对比 maven 中的依赖:
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
<scope>compile</scope>
</dependency>
</dependencies>
项目依赖: 从项目的某个模块依赖另一个模块
implementation project(':subject01')
这种依赖方式是直接依赖本工程中的 libary module,这个 libary module 需要在 setting.gradle 中配置。
本地 jar 依赖:本地 jar 文件依赖,一般包含以下两种方式
//直接依赖某文件
implementation files('libs/foo.jar', 'libs/bar.jar')
//配置某文件夹作为依赖项
implementation fileTree(dir: 'libs', include:
3. 5. 2 、依赖的下载
当执行build命令时,gradle就会去配置的依赖仓库中下载对应的Jar,并应用到项目中。
3. 5. 3 、依赖的类型
类似于Maven的scope标签,gradle也提供了依赖的类型,具体如下所示:
| compileOnly | 由java插件提供,曾短暂的叫provided,后续版本已经改成了compileOnly,适用于编译期需要而不需要打包的情况 |
| runtimeOnly | 由java插件提供,只在运行期有效,编译时不需要,比如mysql驱动包。,取代老版本中被移除的runtime |
| implementation | 由java插件提供,针对源码[src/main目录],在编译、运行时都有效,取代老版本中被移除的compile |
| testCompileOnly | 由java插件提供,用于编译测试的依赖项,运行时不需要 |
| testRuntimeOnly | 由java插件提供,只在测试运行时需要,而不是在测试编译时需要,取代老版本中被移除的testRuntime |
| testImplementation | 由java插件提供,针对测试代码[src/test目录]取代老版本中被移除的testCompile |
| providedCompile | war插件提供支持,编译、测试阶段代码需要依赖此类jar包,而运行阶段容器已经提供了相应的支持,所以无需将这些文件打入到war包中了;例如servlet-api.jar、jsp-api.jar |
| compile | 编译范围依赖在所有的classpath中可用,同时它们也会被打包。在gradle 7. 0 已经移除 |
| runtime runtime | 依赖在运行和测试系统的时候需要,在编译的时候不需要,比如mysql驱动包。在gradle 7. 0 已经移除 |
| api | java-library 插件提供支持,这些依赖项可以传递性地导出给使用者,用于编译时和运行时。取代老版本中被移除的compile |
| compileOnlyApi | java-library插件提供支持,在声明模块和使用者在编译时需要的依赖项,但在运行时不需要。 |
官方文档参考 :
https://docs.gradle.org/current/userguide/java_library_plugin.html#java_library_plugin:各个依赖范围的关系和说明
https://docs.gradle.org/current/userguide/upgrading_version_ 6 .html#sec:configuration_removal:依赖范围升级和移除
https://docs.gradle.org/current/userguide/java_library_plugin.html#java_library_plugin:API和implemention区别
https://docs.gradle.org/current/userguide/java_plugin.html#java_plugin:执行java命令时都使用了哪些依赖范围的依赖。
提示 1 :java插件提供的功能,java-library插件都提供。
3. 5. 4 、api与implementation区别

如下所示:
编译时 :如果libC的内容发生变化,由于使用的是api依赖,依赖会传递,所以libC、libA、projectX都要发生变化,都需要重新编译,速度慢, 运行时: libC、libA、projectX中的class都要被加载。
编译时: 如果libD的内容发生变化,由于使用的是implemetation依赖,依赖不会传递,只有libD、libB要变化并重新编译,速度快, 运行时: libC、libA、projectX中的class都要被加载。
拓展 3 :api和implementation案例分析
api的适用场景是多module依赖,moduleA工程依赖了moduleB,同时moduleB又需要依赖了moduleC,modelA工程也需要去依赖moduleC,这个时候避免重复依赖module,可以使用moduleBapi依赖的方式去依赖moduleC,modelA工程只需要依赖moduleB即可。
总之,除非涉及到多模块依赖,为了避免重复依赖,咱们会使用api,其它情况我们优先选择implementation,拥有大量的api依赖项会显著增加构建时间。
3. 5. 5 、依赖冲突及解决方案
依赖冲突是指"在编译过程中,如果存在某个依赖的多个版本,构建系统应该选择哪个进行构建的问题",如下所示:
A、B、C都是本地子项目module,log 4 j是远程依赖。
编译时: B用 1. 4. 2 版本的log 4 j,C用 2. 2. 4 版本的log 4 j,B和C之间没有冲突 打包时:
只能有一个版本的代码最终打包进最终的A对应的jar|war包,对于Gradle来说这里就有冲突了
案例演示:我们在build.gradle引入依赖库
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' implementation 'org.hibernate:hibernate-core:3.6.3.Final' }

修改build.gradle
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' implementation 'org.hibernate:hibernate-core:3.6.3.Final' implementation 'org.slf4j:slf4j-api:1.4.0' }

如上所示:默认下,Gradle会使用最新版本的jar包【考虑到新版本的jar包一般都是向下兼容的】,实际开发中,还是建议使用官方自带的这种解决方案。 当然除此之外,Gradle也为我们提供了一系列的解决依赖冲突的方法 : exclude移除一个依赖 , 不允许依赖传递 , 强制使用某个版本 。
Exclude排除某个依赖
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' implementation('org.hibernate:hibernate-core:3.6.3.Final'){
//排除某一个库(slf4j)依赖:如下三种写法都行
exclude group: 'org.slf4j' exclude module: 'slf4j-api' exclude group: 'org.slf4j',module: 'slf4j-api' }
//排除之后,使用手动的引入即可。
implementation 'org.slf4j:slf4j-api:1.4.0' }
不允许依赖传递
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' implementation('org.hibernate:hibernate-core:3.6.3.Final'){
//不允许依赖传递,一般不用
transitive(false)
}
//排除之后,使用手动的引入即可
implementation 'org.slf4j:slf4j-api:1.4.0' }
在添加依赖项时,如果设置transitive为false,表示关闭依赖传递。即内部的所有依赖将不会添加到编译和运行时的类路径。
强制使用某个版本
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' implementation('org.hibernate:hibernate-core:3.6.3.Final')
//强制使用某个版本!!【官方建议使用这种方式】
implementation('org.slf4j:slf4j-api:1.4.0!!')
//这种效果和上面那种一样,强制指定某个版本
implementation('org.slf4j:slf4j-api:1.4.0'){ version{
strictly("1.4.0")
}
}
}
拓展 :我们可以先查看当前项目中到底有哪些依赖冲突:
//下面我们配置,当 Gradle 构建遇到依赖冲突时,就立即构建失败
configurations.all() {
Configuration configuration ->
//当遇到版本冲突时直接构建失败
configuration.resolutionStrategy.failOnVersionConflict()
}
3. 6 、Gradle插件
3. 6. 1 、使用插件的原因
简单的说,通过应用插件我们可以:
1.促进代码重用、减少功能类似代码编写、提升工作效率
2.促进项目更高程度的模块化、自动化、便捷化
3.可插拔式的的扩展项目的功能
3. 6. 2 、插件的作用
在项目构建过程中做很多事情,把插件应用到项目中,通常可以完成:
1 、可以添加任务【task】到项目中,从而帮助完成测试、编译、打包等。
2 、可以添加依赖配置到项目中。
3 、可以向项目中拓展新的扩展属性、方法等。
4 、可以对项目进行一些约定,如应用Java插件后,约定src/main/java目录是我们的源代码存在位置,编译时编译这个目录下的Java源代码文件。
3. 6. 3 、插件的分类和使用
第一种:脚本插件
脚本插件的本质就是一个脚本文件,使用脚本插件时通过 apply from:将脚本加载进来就可以了,后面的脚本文件可以是本地的也可以是网络上的脚本文件,下面定义一段脚本,我们在 build.gradle 文件中使用它,具体如下:
//version.gradle文件
ext {
company= "尚硅谷" cfgs = [
compileSdkVersion : JavaVersion.VERSION_1_8
]
spring = [
version : '5.0.0' ]
}
下面将将在构建文件中使用这个脚本文件,具体如下
//build.gradle文件
//map作为参数
apply from: 'version.gradle' task taskVersion{
doLast{
println "公司名称为:${company},JDK版本是${cfgs.compileSdkVersion},版本号是${spring.version}" }
}
上述代码的执行结果如下
S D:\repos\idearepos\root> gradle -q taskVersion 版本是2.0,版本号是1
意义: 脚本文件模块化的基础,可按功能把我们的脚本进行拆分一个个公用、职责分明的文件,然后在主脚本文件引用,比如:将很多共有的库版本号一起管理、应用构建版本一起管理等。
第二种:对象插件之内部插件[核心插件]
二进制插件[对象插件]就是实现了org.gradle.api.Plugin接口的插件,每个JavaGradle插件都有一个plugin id 
可通过如下方式使用一个Java插件:
apply plugin : 'java' //map具名参数方式
或者:
//也可以使用 闭包 作为project.apply方法的一个参数
apply{
plugin 'java' }
通过上述代码就将Java插件应用到我们的项目中了,对于Gradle自带的核心插件都有唯一的pluginid,其中java是Java插件的pluginid,这个pluginid必须是唯一的,可使用应用包名来保证pluginid的唯一性。这里的java对应的具体类型是org.gradle.api.plugins.JavaPlugin,所以可以使用如下方式使用Java插件:
//使用方式1:Map具名参数,全类名
apply plugin:org.gradle.api.plugins.JavaPlugin
//org.gradle.api.plugins默认导入:使用方式2
apply plugin:JavaPlugin
apply plugin: 'java' //核心插件,无需事先引入,使用方式3:插件的id
Gradle中提供的二进制插件【核心插件】,可参考: https://docs.gradle.org/current/userguide/plugin_reference.html
第二种:对象插件之第三方插件
如果是使用第三方发布的二进制插件,一般需要配置对应的仓库和类路径,
//使用传统的应用方式
buildscript { ext {
springBootVersion = "2.3.3.RELEASE"
}
repositories { mavenLocal() maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
jcenter()
}
// 此处先引入插件
dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
//再应用插件
apply plugin: 'org.springframework.boot' //社区插件,需要事先引入,不必写版本号
但是如果是第三方插件已经被托管在https://plugins.gradle.org/ 网站上,就可以不用在buildscript里配置classpath依赖了,直接使用新出的pluginsDSL的方式引用,案例如下:
plugins {
id 'org.springframework.boot' version '2.4.1' }
使用pluginsDSL方式
注意 :
1 .如果使用老式插件方式buildscript{}要放在build.gradle文件的最前面,而新式plugins{}没有该限制。
2 .托管在网站gradle插件官网的第三方插件有两种使用方式,一是传统的buildscript方式,一种是pluginsDSL方式。
第二种:对象插件之用户自定义插件
interface GreetingPluginExtension {
Property<String> getMessage()
Property<String> getGreeter()
}
class GreetingPlugin implements Plugin<Project> { void apply(Project project) {
def extension = project.extensions.create('greeting', GreetingPluginExtension)
project.task('hello') {
doLast {
println "${extension.message.get()} from ${extension.greeter.get()}"
}
}
}
}
apply plugin: GreetingPlugin
// Configure the extension using a DSL block
greeting { message = 'Hi' greeter = 'Gradle' }
参考地址:https://docs.gradle.org/current/userguide/custom_plugins.html
我们直接执行hello任务./gradlehello即可,这种方式实现的插件我们一般不使用,因为这种方式局限性太强,只能本Project,而其他的Project不能使用。
3. 6. 4 、buildSrc项目
buildSrc是Gradle默认的插件目录,编译Gradle的时候会自动识别这个目录,将其中的代码编译为插件。
1 .首先先建立一个名为buildSrc的javaModule,将buildSrc从includedmodules移除,重新构建,然后只保留build.gradle和src/main目录,其他全部删掉,注意名字一定是buildSrc,不然会找不到插件。
2 .然后修改Gradle中的内容
apply plugin: 'groovy' //必须
apply plugin: 'maven-publish' dependencies {
implementation gradleApi() //必须
implementation localGroovy() //必须
}
repositories {
google()
jcenter() mavenCentral() //必须
}
//把项目入口设置为src/main/groovy
sourceSets { main {
groovy {
srcDir 'src/main/groovy' }
}
}
3 .创建入口目录,在src/main下创建代码入口目录,如下:
4 .然后实现插件代码Text.groovy,注意文件后缀为groovy,文件要引入packagecom.atguigu
package com.atguigu
import org.gradle.api.Plugin
import org.gradle.api.Project class Text implements Plugin<Project>{
@Override void apply(Project project) {
project.task("atguigu"){
doLast{
println("自定义atguigu插件")
}
}
}
}
5 .接下来在main目录下创建resources目录,在resources目录下创建META-INF目录,在META-INF目录下创建gradle-plugins目录,在gradle-plugins目录下创建properties文件
6 .properties文件可以自己命名,但是要以.properties结尾,比如com.atguigu.plugin.properties,其com.atguigu.plugin就是定义的包名路径.
7 .最后需要在properties文件中指明我们实现插件的全类implementation-class=com.atguigu.Text
到目前为止我们的插件项目已经写完了,在module引入我们写的插件applyplugin:‘com.atguigu.plugin’,然后执行插件的
Task,./gradleatguigu
输出:
这种形式的写法,在我们整个工程的 module 都可以使用,但也只是限制在本工程,其他工程不能使用。
改进:
第二种写插件的方式他只能在本工程中使用,而其他的项目工程不能使用,有时候我们需要一个插件在多个工程中使用,
这时候我们就需要把插件上传 maven 中。
第一步: 首先将上述 buildSrc 目录复制一份,修改文件夹名,然后在 settings.gradle 文件中使用 include 引入
第二步:修改 build.gradle 文件,发布到 maven 仓库中
apply plugin: 'groovy' //必须
apply plugin: 'maven-publish' dependencies {
implementation gradleApi() //必须
implementation localGroovy() //必须
}
repositories {
google()
jcenter() mavenCentral() //必须
}
sourceSets { //把项目入口设置为src/main/groovy main {
groovy {
srcDir 'src/main/groovy' }
}
}
publishing {
publications { myLibrary(MavenPublication) {
groupId = 'com.atguigu.plugin' //指定GAV坐标信息
artifactId = 'library' version = '1.1' from components.java//发布jar包
//from components.web///引入war插件,发布war包
}
}
repositories { maven { url "$rootDir/lib/release" }
//发布项目到私服中
// maven {
// name = 'myRepo' //name属性可选,表示仓库名称,url必填
// //发布地址:可以是本地仓库或者maven私服
// //url = layout.buildDirectory.dir("repo")
// //url='http://my.org/repo' // // change URLs to point to your repos, e.g. http://my.org/repo // //认证信息:用户名和密码
// credentials {
// username = 'joe' // password = 'secret' // }
// }
}
}
第三步:执行publish指令,发布到根project或者maven私服仓库。
第四步:使用插件,在项目级build.gradle文件中将插件添加到classpath:
apply plugin: 'groovy' //必须
apply plugin: 'maven-publish' dependencies {
implementation gradleApi() //必须
implementation localGroovy() //必须
}
repositories {
google()
jcenter() mavenCentral() //必须
}
sourceSets { //把项目入口设置为src/main/groovy main {
groovy {
srcDir 'src/main/groovy' }
}
}
publishing {
publications { myLibrary(MavenPublication) {
groupId = 'com.atguigu.plugin' //指定GAV坐标信息
artifactId = 'library' version = '1.1' from components.java//发布jar包
//from components.web///引入war插件,发布war包
}
}
repositories { maven { url "$rootDir/lib/release" }
//发布项目到私服中
// maven {
// name = 'myRepo' //name属性可选,表示仓库名称,url必填
// //发布地址:可以是本地仓库或者maven私服
// //url = layout.buildDirectory.dir("repo")
// //url='http://my.org/repo' // // change URLs to point to your repos, e.g. http://my.org/repo // //认证信息:用户名和密码
// credentials {
// username = 'joe' // password = 'secret' // }
// }
}
}
第五步:执行gradlebuil在这里插入代码片d指令就会在控制台看到自定义插件的输出,说明自定义插件就已经生效了。
最后,至于如何写一个插件,能帮助项目更加自动化或者便捷化,是值得大家未来需要长期思考、关注、努力的点。
3. 6. 5 、插件的关注点
第一点:插件的引用
apply plugin:'插件名'
第二点:主要的功能[任务]
当我们在工程中引入插件后,插件会自动的为我们的工程添加一些额外的任务来完成相应的功能。以Java插件为例,当我们加入java插件之后,就加入了如下功能:

具体大家可通过gradletasks查看加入某个插件前后的区别。
说明:Gradle中的任务依赖关系是很重要的,它们之间的依赖关系就形成了构建的基本流程。
第三点:工程目录结构
一些插件对工程目结构有约定,所以我们一般遵循它的约定结构来创建工程,这也是Gradle的“约定优于配置”原则。例如java插件规定的项目源集目录结构如下所示:
如果要使用某个插件就应该按照它约定的目录结构设置,这样能大大提高我们的效率,当然各目录结构也可以自己定义。
第四点:依赖管理
比如前面我们提到的 依赖的类型[依赖管理]部分,不同的插件提供了不同的依赖管理
buildscript{
repositories{
maven{url"$rootDir/lib/release"}
}
dependencies{
classpath"com.atguigu.plugin:library: 1. 1 "
}
}
applyplugin:‘java’
//是在atguiguplugin中定义的插件ID
applyplugin:‘com.atguigu.plugin’
比如前面我们提到的依赖的类型[依赖管理]部分,不同的插件提供了不同的依赖管理。
第五点:常用的属性
例如:Java插件会为工程添加一些常用的属性,我们可以直接在编译脚本中直接使用。
属性名称 类型 默认值 描述
reportsDirName String |reports 生成报告的目录名称
reportsDir File(只读)buildDir/reportsDirName 生成报告的目录
testResultsDirName String test-results 生成测试result.xml文件的目录名称
testResultsDir File(只读)reportsDir/testReportDirName 生成测试报告的目录
libsDirName String libs 生成lib库的目录名称
libsDir File(只读)buildDir/libsDirName 生成lib库的目录
distsDirName String distributions 生成发布文件的目录名称
distsDir File(只读)buildDir/distsDirName 生成发布文件的目录
docsDirName String docs 生成帮助文档的目录名称
docsDir File(只读)buildDir/docsDirName 生成帮助文档的目录
dependencyCacheDirNameString dependency-cache 存储缓存资源依赖信息的目录名称
dependencyCacheDir File(只读)buildDir/dependencyCacheDirName存储缓存资源依赖信息的目录
(上面可进行复制)
当然,这里还有一些其它属性
| 属性名称 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| sourceSets | SourceSetContainer(只读) | Notnull | 包含工程的资源集(sourcesets.) |
| sourceCompatibility | JavaVersion,也可以使用字符串或数字,比如’ 1. 5 '或者 1. 5 | 根据使用的JVM定 | 编译java文件时指定使用的java版本 |
| targetCompatibility | JavaVersion,也可以使用字符串或数字,比如’ 1. 5 '或者 1. 5 | sourceCompatibility | 生成classes的java版本 |
| archivesBaseName | String | projectName | 作为归档文件的默认名称,如AR或者ZIP文件的名称 |
3. 6. 6 、Java插件分析
参考官网:https://docs.gradle.org/current/userguide/plugin_reference.html,以Java插件为例,讲解需要关注的几点:
第一点:我们要关注插件使用
plugins {
id 'java' }
第二点:我们要关注插件的功能
我们可通过官方文档介绍了解某个插件功能或者百度、再或者大家可以通过gradletasks查看加入java插件前后的区别。
第三点:项目布局
一般加入一个插件之后,插件也会提供相应的目录结构,例如:java插件的目录结构

当然这个默认的目录结构也是可以改动的例如:
sourceSets { main {
java {
srcDirs = ['src/java']
}
resources {
srcDirs = ['src/resources']
}
}
}
也可设置源集的属性等信息。
第四点:依赖管理:以java插件为例,提供了很多依赖管理项
源集依赖关系配置
测试源集依赖关系配置
第五点:额外的属性和方法 :
可参考官方文档:sourceCompatibility(JavaVersion. VERSION_ 1 _ 8 )
3. 7 、build.gradle文件
build.gradle是一个gradle的构建脚本文件,支持java、groovy等语言。
每个project都会有一个build.gradle文件,该文件是项目构建的入口,可配置版本、插件、依赖库等信息。
每个build文件都有一个对应的Project实例,对build.gradle文件配置,本质就是设置Project实例的属性和方法。
由于每个project都会有一个build文件,那么RootProject也不列外.RootProject可以获取到所有ChildProject,所以在RootProject的build文件中我们可以对ChildProject统一配置,比如应用的插件、依赖的maven中心仓库等。
build文件中常见的属性和方法如下所示:

3. 7. 1 、常见属性代码
代码参考:
//指定使用什么版本的JDK语法编译源代码,跟编译环境有关,在有java插件时才能用
sourceCompatibility = 1.8
//指定生成特定于某个JDK版本的class文件:跟运行环境有关,在有java插件时才能用
targetCompatibility = 1.8
//业务编码字符集,注意这是指定源码解码的字符集[编译器]
compileJava.options.encoding "UTF-8"
//测试编码字符集,注意这是指定源码解码的字符集[编译器]
compileTestJava.options.encoding "UTF-8"
//编译JAVA文件时采用UTF-8:注意这是指定源码编码的字符集【源文件】
tasks.withType(JavaCompile) { options.encoding = "UTF-8"
}
//编译JAVA文件时采用UTF-8:注意这是指定文档编码的字符集【源文件】
tasks.withType(Javadoc) { options.encoding = "UTF-8"
}
提示 1 :group+name+version类似于maven的group+artifactId+version
提示 2 :encoding解决业务代码与测试代码中文乱码问题
3. 7. 2 、Repositories
repositories {
//gradle中会按着仓库配置的顺序,从上往下依次去对应的仓库中找所需要的jar包: //如果找到,则停止向下搜索,如果找不到,继续在下面的仓库中查找
//指定去本地某个磁盘目录中查找:使用本地file文件协议:一般不用这种方式
maven { url 'file:///D:/repos/mavenrepos3.5.4'} maven { url "$rootDir/lib/release" }
//指定去maven的本地仓库查找
mavenLocal()
//指定去maven的私服或者第三方镜像仓库查找
maven { name "Alibaba" ; url "https://maven.aliyun.com/repository/public" } maven { name "Bstek" ; url "https://nexus.bsdn.org/content/groups/public/" }
//指定去maven的远程仓库查找:即 https://repo.maven.apache.org/maven2/ mavenCentral()
//去google仓库查找
google()
}
因为Gradle没有自己的远程仓库,而是使用Maven、jcenter、jvy、google这些远程仓库。
3. 7. 3 、Subprojects与Allprojects
allprojects是对所有project (包括RootProject+childProject[当前工程和所有子工程]) 的进行统一配置,而subprojects是对 所有ChildProject的进行统一配置 。
测试如下:
allprojects {
tasks.create('hello') {
doLast {
task ->
println "project name is $task.project.name" }
}
}
subprojects {
hello.doLast{
task->
println "here is subprojects $task.project.name" }
}
通常在subprojects和allprojects中:
allprojects(){ //本质Project中的allprojects方法,传递一个闭包作为参数。
apply plugin: 'java' ext {
junitVersion = '4.10'
.. }
task allTask{
... }
repositories {
... }
dependencies {
... }
}
subprojects(){ …//同上面allprojects中的方法。
}
拓展 1 : 如果是直接 在根project配置 repositories和dependencies则 只针对根工程有效。
拓展: 我们也可以在对单个Project进行单独配置:
project('subject01') {
task subject01 {
doLast {
println 'for subject01' }
}
}
执行gradlebuild指令即可查看测试效果。
3. 7. 4 、ext用户自定义属性
Project和Task都允许用户添加额外的自定义属性,要添加额外的属性,通过应用所属对象的ext属性即可实现。添加之后可以通过ext属性对自定义属性读取和设置,如果要同时添加多个自定义属性,可以通过ext代码块:
//自定义一个Project的属性
ext.age = 18
//通过代码块同时自定义多个属性
ext {
phone = 19292883833
address="北京尚硅谷"
}
task extCustomProperty {
//在task中自定义属性
ext {
desc = "奥利给"
}
doLast {
println "年龄是:${age}"
println "电话是:${phone}"
println "地址是:${address}"
println "尚硅谷:${desc}"
}
}
测试:通过gradle extCustomProperty
输出结果为:
年龄是:18
电话是:19292883833 地址是:北京尚硅谷
尚硅谷:奥利给
拓展 1 :ext配置的是用户自定义属性,而gradle.properties中一般定义系统属性、环境变量、项目属性、JVM相关配置信息。例如
gradle.properties文件案例:加快构建速度的,gradle.properties文件中的属性会自动在项目运行时加载。
## 设置此参数主要是编译下载包会占用大量的内存,可能会内存溢出
org.gradle.jvmargs=-Xms4096m -Xmx8192m
## 开启gradle缓存
org.gradle.caching=true #开启并行编译
org.gradle.parallel=true #启用新的孵化模式
org.gradle.configureondemand=true #开启守护进程
org.gradle.daemon=true
详细请参考:https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties
3. 7. 5 、Buildscript
buildscript里是gradle脚本执行所需依赖,分别是对应的maven库和插件。
案例如下:
import org.apache.commons.codec.binary.Base64
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath group: 'commons-codec', name: 'commons-codec', version: '1.2' }
}
tasks.register('encode') {
doLast {
def byte[] encodedString = new Base64().encode('hello world\n'.getBytes())
println new String(encodedString)
}
}
需要注意的是:
1 .buildscript{}必须在build.gradle文件的最前端。 2
.对于多项目构建,项目的buildscript()方法声明的依赖关系可用于其所有子项目的构建脚本。 3
.构建脚本依赖可能是Gradle插件。
案例如下所示:
//老式apply插件的引用方式,使用apply+buildscript
buildscript {
ext {
springBootVersion = "2.3.3.RELEASE" }
repositories {
mavenLocal() maven {
url 'http://maven.aliyun.com/nexus/content/groups/public' }
jcenter()
}
//此处引入插件
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java' //核心插件,无需事先引入
apply plugin: 'org.springframework.boot' //社区插件,需要事先引入,才能应用,不必写版本号
3. 8 、publishing 项目发布
接下来,将咱们写好的模块发布发布到公司的私服以供别人使用,如下所示:
3. 8. 1 、引入maven发布的插件
plugins {
id 'java-library' //如果发布war包,需要war插件,java-library支持带源码、文档发布
id 'maven-publish' }
3. 8. 2 、设置发布代码
//带源码和javadoc的发布:需要'java-library'插件支持:它是java的升级版,java插件的功能java-library都有
//javadoc.options.encoding="UTF-8" //java {
// withJavadocJar()
// withSourcesJar()
//}
publishing {
publications {
myLibrary(MavenPublication) {
groupId = 'org.gradle.sample' //指定GAV坐标信息
artifactId = 'library' version = '1.1'
from components.java//发布jar包
//from components.web///引入war插件,发布war包
}
}
repositories {
//本地仓库位于USER_HOME/.m2/repository mavenLocal()
//发布项目到私服中
maven {
name = 'myRepo' //name属性可选,表示仓库名称,url必填
//发布地址:可以是本地仓库或者maven私服
//url = layout.buildDirectory.dir("repo")
// change URLs to point to your repos, e.g. http://my.org/repo def releasesRepoUrl = layout.buildDirectory.dir('repos/releases')
def snapshotsRepoUrl = layout.buildDirectory.dir('repos/snapshots') url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
//认证信息:用户名和密码
// credentials {
// username = 'joe' // password = 'secret' // }
}
}
}
3. 8. 3 、执行发布指令
执行发布命令,将项目发布到本地仓库或者远程仓库。常见的发布指令有:
generatePomFileForPubNamePublication:生成pom文件
publishPubNamePublicationToRepoNameRepository:发布项目到指定仓库,如果没有仓库名,默认为maven
publishPubNamePublicationToMavenLocal:将PubName发布复制到本地Maven仓库中包括POM文件和其他元数据。
publish:发布到repositories中指定的仓库(为比如Maven私服)
publishToMavenLocal:执行所有发布任务中的操作发布到本地maven仓库【默认在用户家目录下的.m 2
/repository】。
3. 9 、生命周期中Hook
生命周期中的这些钩子函数都是由gradle自动回调完成的,利用这些钩子函数可以帮助我们实现一些我们想要的功能。

Gradle 在生命周期各个阶段都提供了用于回调的钩子函数: Gradle 初始化阶段:
- 在 settings.gradle 执行完后,会回调 Gradle 对象的 settingsEvaluated 方法
- 在构建所有工程 build.gradle 对应的 Project 对象后,也既初始化阶段完毕,会回调 Gradle 对象的 projectsLoaded 方法
Gradle 配置阶段: Gradle 会循环执行每个工程的 build.gradle 脚本文件 - 在执行当前工程 build.gradle 前,会回调 Gradle 对象的 beforeProject 方法和当前 Project 对象的 beforeEvaluate 方法, 虽然 beforeEvalute 属于 project 的生命周期, 但是此时 build script 尚未被加载, 所beforeEvaluate 的设置依然要在 init script 或 setting script 中进行,不要在 build script 中使用 project.beforeEvaluate 方法。
- 在执行当前工程 build.gradle 后,会回调 Gradle 对象的 afterProject 方法和当前 Project 对象的 afterEvaluate 方法
- 在所有工程的 build.gradle 执行完毕后,会回调 Gradle 对象的 projectsEvaluated 方法
- 在构建 Task 依赖有向无环图后,也就是配置阶段完毕,会回调 TaskExecutionGraph 对象的 whenReady 方法
Gradle 执行阶段: Gradle 会循环执行 Task 及其依赖的 Task - 在当前 Task 执行之前,会回调 TaskExecutionGraph 对象的 beforeTask 方法
- 在当前 Task 执行之后,会回调 TaskExecutionGraph 对象的 afterTask 方法
当所有的 Task 执行完毕后,会回调 Gradle 对象的 buildFinish 方法。
提示:Gradle 执行脚本文件的时候会生成对应的实例,主要有如下几种对象:
1、Gradle 对象:在项目初始化时构建,全局单例存在,只有这一个对象
2、Project 对象:每一个build.gradle文件 都会转换成一个 Project 对象,类似于maven中的pom.xml文件
3、Settings 对象:settings.gradle 会转变成一个 settings 对象,和整个项目是一对一的关系,一般只用到include方法
4.、Task对象: 从前面的有向无环图中,我们也可以看出,gradle最终是基于Task的,一个项目可以有一个或者多个Task
钩子函数代码演示:项目目录结构如下

在 root project 的 settings.gradle 文件中添加:
gradle.settingsEvaluated {
//1.settingsEvaluated钩子函数,在初始化阶段完成
println "settingsEvaluated"
}
gradle.projectsLoaded {
//2.projectsLoaded钩子函数,在初始化阶段完成
println "projectsLoaded"
}
//声明一个变量:表示当前项目名,在每次执行某个项目的beforeEvaluate方法时先给projectName变量赋值
//这样方便在:gradle.beforeProject和afterProject两个钩子函数使用。
def projectName=""
gradle.addProjectEvaluationListener( new ProjectEvaluationListener(){
//3.执行各个project的beforeEvaluate:在配置阶段完成
@Override void beforeEvaluate(Project project) {
projectName=project.name println "${project.name} Project beforeEvaluate"
}
//5.执行各个project的afterEvaluate:在配置阶段完成
@Override void afterEvaluate(Project project, ProjectState projectState) {
println "${project.name} Project afterEvaluate"
}
}); gradle.beforeProject {
//4.执行各个project的beforeProject:在配置阶段完成
println "${projectName} beforeProject..."
}
gradle.afterProject {
//6.执行各个project的afterProject:在配置阶段完成
println "${projectName} afterProject..."
}
//7.所有工程的 build.gradle 执行完毕后,回调 Gradle 对象的 projectsEvaluated 方法:在配置阶段完成
def rootProjectName=rootProject.getName()
gradle.projectsEvaluated {
println "${rootProjectName} projectsEvaluated..."
}
//8.配置阶段完毕后,回调 TaskExecutionGraph 对象的 whenReady 方法:在配置阶段完成
gradle.taskGraph.whenReady {
println "${rootProjectName} taskGraph whenReady..."
}
//9.在当前Task执行之前,会回调 TaskExecutionGraph 对象的 beforeTask方法:在执行阶段完成
gradle.taskGraph.beforeTask {
task ->
println "this is the task ${task.name} of the project ${task.getProject().name} beforeTask.."
}
//10.在当前Task执行之后,会回调 TaskExecutionGraph 对象的 afterTask方法:在执行阶段完成
gradle.taskGraph.afterTask {
task ->
println "this is the task ${task.name} of the project ${task.getProject().name} afterTask.."
}
//11.当所有的 Task 执行完毕后,会回调 Gradle 对象的 buildFinish 方法:在执行阶段完成
gradle.buildFinished {
println "${rootProjectName} buildFinished..."
}
在 root 的 build.gradle
task A {
println "root taskA" doFirst(){
println "root taskA doFirst" }
doLast(){
println "root taskA doLast" }
}
在 subject01 的 build.gradle
task B {
println "SubProject01 taskB" doFirst(){
println "SubProject01 taskB doFirst" }
doLast(){
println "SubProject01 taskB doLast" }
}
在 subject02 的 build.gradle 文件中添加
//task C在上面
task C{
//依赖task D
dependsOn 'D' println "SubProject02 taskC" doFirst(){
println "SubProject02 taskC doFirst" }
doLast(){
println "SubProject02 taskC doLast" }
}
//task D在下面
task D {
println "SubProject02 taskD" doFirst(){
println "SubProject02 taskD doFirst" }
doLast(){
println "SubProject02 taskD doLast" }
}
测试:在 root 工程的根目录执行:gradle C .就能看到 gradle 生命周期的三个阶段,及每个阶段执行的钩子函数、还有在
执行阶段有依赖关系的任务的执行顺序问题。
拓展 1:在 settings.gradle 中添加监听器,查看 task 有向无环图:
gradle.taskGraph.addTaskExecutionGraphListener(new TaskExecutionGraphListener() {
@Override //生成有向无环图
void graphPopulated(TaskExecutionGraph taskExecutionGraph) {
taskExecutionGraph.allTasks.forEach(task->{
//核心逻辑:通过taskExecutionGraph获得所有的task
taskExecutionGraph.allTasks.forEach(releaseTask->{
println "尚硅谷:" + releaseTask.getProject().name + ":" + releaseTask.name })
})
}
})
测试:在 root 工程根目录下执行:gradle

拓展 2: 计算 Gradle 构建过程中各个阶段的耗时:需要注意,这里只是计算了初始化阶段的 settings 文件,并没有计算init.gradle 初始化的时间。
def projectName=rootProject.getName() //定义项目名
long beginOfSetting = System.currentTimeMillis() //初始化阶段开始时间
def beginOfConfig //配置阶段开始时间
def configHasBegin = false //配置阶段是否开始了,只执行一次
def beginOfProjectConfig = new HashMap() //存放每个 build.gradle 执行之前的时间
def beginOfTaskExecute //执行阶段开始时间
gradle.projectsLoaded {
//初始化阶段执行完毕
println "${projectName}工程 初始化总耗时 ${System.currentTimeMillis() - beginOfSetting} ms"
}
//build.gradle 执行前
gradle.beforeProject {
Project project ->
if(!configHasBegin){
configHasBegin = true beginOfConfig = System.currentTimeMillis()
}
beginOfProjectConfig.put(project,System.currentTimeMillis())
}
//build.gradle 执行后
gradle.afterProject {
Project project ->
def begin = beginOfProjectConfig.get(project)
if(project.name == projectName) {
println "根工程${projectName} 配置阶段耗时:${System.currentTimeMillis() - begin} ms"
}else{
println "子工程${project.name} 配置阶段耗时:${System.currentTimeMillis() - begin} ms"
}
}
gradle.taskGraph.whenReady {
//配置阶段完毕
println "整个${projectName}项目在配置阶段总耗时:${System.currentTimeMillis() - beginOfConfig} ms"
beginOfTaskExecute = System.currentTimeMillis()
}
//执行阶段开始
gradle.taskGraph.beforeTask {
Task task ->
task.doFirst {
task.ext.beginOfTask = System.currentTimeMillis()
}
task.doLast {
println "${task.name}在执行阶段耗时:${System.currentTimeMillis() - task.ext.beginOfTask} ms"
}
}
gradle.buildFinished {
//执行阶段完毕
println " 执行阶段总耗时:${System.currentTimeMillis() - beginOfTaskExecute} ms"
println " 整个构建过程耗时:${System.currentTimeMillis() - beginOfSetting} ms"
}
3. 10 、创建Springboot项目
SpringBootGradle插件在Gradle提供SpringBoot支持。它允许您打包可执行jar或war归档文件,运行SpringBoot应用程序,并使用Spring-Boot-dependencies提供的依赖管理。
相关文档请参考:https://docs.spring.io/spring-boot/docs/current/gradle-plugin/reference/htmlsingle/#running-your-application
3. 10. 1 、引入springboot插件
该插件发布在Gradle的插件门户网站上,可以使用插件块来应用:
https://docs.spring.io/spring-boot/docs/current/gradle-plugin/reference/htmlsingle/#running-your-application
3. 10. 2 、引入所需要的依赖
dependencies {
implementation 'org.springframework.boot:spring-boot-starter' implementation 'org.springframework.boot:spring-boot-starter-web' //省略版本,原生bom支持,插件management提供
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' }
}
test {
useJUnitPlatform()
}
3. 10. 3 、执行gradle bootRun指令
要想运行当前Springboot项目,直接执行gradlebootRun指令或者idea右侧按钮即可。当然如果想让当前项目打成可执行jar包,只需执行:gradlebootJar指令即可。
Cloud项目创建也可以借助于脚手架创建,与Boot项目类似。
3. 10. 4 、拓展spring-boot-gradle-plugin插件
buildscript {
repositories {
maven {
url 'https://maven.aliyun.com/repository/public' }
}
dependencies {
classpath 'org.springframework.boot:spring-boot-gradle-plugin:2.4.1' }
}
applyplugin:‘org.springframework.boot’
applyplugin:‘io.spring.dependency-management’
3. 11 、基于ssm多模块项目案例
3. 11. 1 、多项目模块划分

meinian-mobile-web:美年旅游项目的用户系统
meinian-web: 美年旅游项目的管理员系统
meinian-service: 美年旅游项目的业务逻辑层
meinian-dao : 美年旅游项目的持久化层
meinian-bean : 美年旅游项目的Model封装
3. 11. 2 、项目搭建前配置分析
3. 11. 3 、代码演示
代码和配置文件同单体ssm一样。只不过做了拆分。
3. 11. 4 、settings.gradle文件中
rootProject.name='meinian-parent'
include'meinian-bean'
include'meinian-dao'
include'meinian-service'
include'meinian-web'
include'meinian-mobile-web'
3. 11. 5 、在根工程build.gradle文件中抽取子模块的公共配置
group 'com.atguigu' version '1.0-SNAPSHOT' subprojects {
//添加插件
apply plugin: 'java' //基本JDK配置
sourceCompatibility = 1.8
targetCompatibility = 1.8
compileJava.options.encoding "UTF-8" compileTestJava.options.encoding "UTF-8"
tasks.withType(JavaCompile) {
options.encoding = "UTF-8" }
group 'com.atguigu' version '1.0-SNAPSHOT' repositories {
mavenLocal() maven {
url "https://maven.aliyun.com/repository/public"} maven {
url "https://maven.aliyun.com/repository/central"} maven {
url "https://maven.aliyun.com/repository/google"} maven {
url "https://maven.aliyun.com/repository/spring"} mavenCentral()
}
//依赖的配置:设置通用的依赖
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' implementation 'log4j:log4j:1.2.17' }
test {
useJUnitPlatform()
}
}
3. 11. 6 、在根工程的build.gradle文件中配置各个模块的依赖信息
project("meinian-bean"){
dependencies {
compileOnly 'org.projectlombok:lombok:1.18.24' }
}
project("meinian-dao"){
apply plugin: 'java-library'//支持api
dependencies {
api project(':meinian-bean')
implementation 'org.mybatis:mybatis-spring:1.2.3' implementation 'com.alibaba:druid:1.0.15' implementation 'org.mybatis:mybatis:3.3.0' implementation 'mysql:mysql-connector-java:5.1.36' }
}
project("meinian-service"){
apply plugin: 'java-library'//支持api
dependencies {
api project(':meinian-dao')
implementation 'org.springframework:spring-web:4.1.7.RELEASE' implementation 'org.springframework:spring-test:4.0.5.RELEASE' implementation 'org.springframework:spring-jdbc:4.1.7.RELEASE' implementation 'org.aspectj:aspectjweaver:1.8.6' }
}
project("meinian-web"){
apply plugin: 'war' dependencies {
implementation project(':meinian-service')
implementation 'org.springframework:spring-webmvc:4.1.7.RELEASE' implementation "com.fasterxml.jackson.core:jackson-databind:2.2.3"
implementation "com.fasterxml.jackson.core:jackson-annotations:2.2.3"
implementation "com.fasterxml.jackson.core:jackson-core:2.2.3" compileOnly 'javax.servlet:servlet-api:2.5' implementation 'jstl:jstl:1.2' }
}
project("meinian-mobile-web"){
apply plugin: 'war' dependencies {
//implementation project(':meinian-bean')
implementation project(':meinian-service')
implementation 'org.springframework:spring-webmvc:4.1.7.RELEASE' implementation "com.fasterxml.jackson.core:jackson-databind:2.2.3"
implementation "com.fasterxml.jackson.core:jackson-annotations:2.2.3"
implementation "com.fasterxml.jackson.core:jackson-core:2.2.3" compileOnly 'javax.servlet:servlet-api:2.5' implementation 'jstl:jstl:1.2' }
}
抽取之后,各子模块的build.gradle文件就不用配置了。
3. 12 、微服务实战
3. 12. 1 、创建数据库及表
创建对应的数据库:CREATEDATABASEmicro_user
CREATE TABLE USER(
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, username VARCHAR(30) NOT NULL COMMENT '用户名', email VARCHAR(30) NOT NULL COMMENT '邮箱', gender TINYINT DEFAULT 0 COMMENT '性别' )
创建对应的数据库:CREATE
USE micro_order; CREATE TABLE `orderinfo` ( `oid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `uid` INT(11) DEFAULT NULL COMMENT '用户id', `product_name` VARCHAR(30) NOT NULL COMMENT '省份', PRIMARY KEY (`oid`)) ENGINE=INNODB AUTO_INCREMENT=5 DEFAULT CHARSET=utf
3. 12. 2 、搭建项目架构

microservice-parent:统一管理所有模块的jar包版本信息
microservice-bean: 统一管理所有模块的用到的pojo类
microservice-common:统一管理所有模块的用到的工具类、枚举类、异常处理、日志文件、统一返回结果信息
microservice-service: 统一封装所有的微服务
microservice-gateway: 封装网关信息
3. 12. 3 、具体演示
创建项目结构,记得将gradle改成本地自己安装的gradle。详情请参考视频。
边栏推荐
- leetcode:不可能得到的最短骰子序列【思维题 + 分组思想】
- Can bank financial products be redeemed and transferred out on the same day?
- 731. My schedule II (segment tree or scoring array)
- Thank Huawei for sharing the developer plan
- Dtable launched in the public beta, which is not only a table, but also a business application builder
- Es+redis+mysql, the high availability architecture design is awesome! (supreme Collection Edition)
- Mysql database commands
- Maxcompute instance related operations
- Is it safe to open an account on Alipay
- 微机原理:CPU架构详解
猜你喜欢

Binary search

Thread pool learning

From front-line development to technical director, you are almost on the shelf

【MLFP】《Face Presentation Attack with Latex Masks in Multispectral Videos》

Brand new: the latest ranking of programming languages in July

Feeding Program Source Code to ZK VMs

【二分好题】
![[blind box app mall system] function introduction after online unpacking](/img/c9/3d13409113a3671555c4744eeb3913.jpg)
[blind box app mall system] function introduction after online unpacking

Sqlserver BCP parameter interpretation, character format selection and fault handling summary

Easily make 2D tile map with unity tilemap - Basics
随机推荐
损失函数之Diou和Ciou loss
《论文复现》BiDAF代码实现过程(4)模型训练+验证
Drawing library Matplotlib drawing
01_ UE4 advanced_ PBR material
Both Chen Chunhua and Mo Yan have words of suffering
Class notes (4) (3) -573. Lecture hall arrangement (Hall)
陈春花与莫言,都有苦难言
Is it safe to open an account on Alipay
[Development Tutorial 4] open source Bluetooth heart rate waterproof sports Bracelet - external flash reading and writing
Image processing notes (1) image enhancement
Sqlserver BCP parameter interpretation, character format selection and fault handling summary
"Iruntime": undeclared identifier
2022 Tsinghua summer school notes L2_ 1 basic composition of neural network
Conditional judgment of Shell Foundation
What is a database password?
Today's nft/ digital collection hotspot
Classical review: understanding the "knowledge consistency" of neural networks (ICLR 2020)
Unity & facegood audio2face drives face blendshape with audio
A simple method -- determine whether the dictionary has changed
Information System Project Manager - Chapter 10 project communication management and project stakeholder management