文章目录

  • 一、Gradle基础
    • Gradle的基础概念
      • Distribution
      • Wrapper
      • GradleUserHome
      • Daemon
      • 总结
    • Groovy基础
      • 动态调用与MOP
      • 闭包
  • 二、Gradle构建
    • Gradle的核心模型
      • Lifecycle
      • Setting
      • Project
      • Task
      • TaskContainer
      • Hook
  • 三、Gradle插件的编写
    • 构建逻辑的复用
    • 简单插件
    • Script插件
    • BuildSrc插件
    • Binary(二进制)插件
  • 四、实际插件分析
  • 五、补充-java插件的war
  • 六、补充-java插件的test
  • 七、补充-idea插件
  • 八、补充-gradle.properties文件

本笔记对应的学习视频连接: 来自Gradle开发团队的Gradle入门教程,不适合纯小白观看,建议先看一下这一篇博客: Gradle 功能及使用详解。

一、Gradle基础

Gradle的基础概念

Distribution

即Gradle的发布包,下载路径:https://services.gradle.org/,Gradle在2019年开启了中国区的CDN,所以在中国区下载Gradle也很快了。Gradle有三种类型的发布包。

  • Gradle-src:Gradle的源码包,在https://github.com/gradle/gradle上也能找到。
  • Gradle-bin:包含Gradle的可运行程序
  • Gradle-all: 包含Gradle的可运行程序、用户文档、sample

我们一般不通过Distribution方式来下载Gradle,而是通过Wrapper方式(后面会讲到)。

Gradle的目录结构如下

跟Maven、Groovy很像(因为他们都是基于JVM的程序),bin/gradle是linux环境的启动脚本,bin/gradle.bat是windows环境的启动脚本,会启动一个jvm并加载lib目录下的所有jar(都是Gradle运行所需要的库)。

Wrapper

参考官方文档The Gradle Wrapper。

Gradle的发布速度非常快,目前基本上每6周发布一次,新的版本难免会不兼容旧版本,所以为了在不同Gradle版本之中保持稳定的项目构建,就需要gradle wrapper。wrapper 有版本区分,但是并不需要你手动去下载(简化了Gardle的安装和部署),当你运行脚本的时候,如果本地没有部署Gardle就使用gradle wrapper命令自动下载对应版本文件。

使用命令运行包装任务,并提供选项:

//  --gradle-version 6.8.2 --distribution-type all
$ gradle wrapper --gradle-version 6.8.2 --distribution-type all
> Task :wrapper
BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed

为了使包装器文件对其他开发人员和执行环境可用,您需要将它们签入版本控制中。所有包装文件,都包括一个非常小的JAR文件,请将JAR文件添加到版本控制中。有些组织不允许项目向版本控制提交二进制文件。目前,除了该方法之外,没有其他选择可供选择。

该命令会在你的Gradle安装目录下生成一个很小的包装器jar文件、一个包装器properties文件和两个批处理文件(linux版、windows版)。让我们看看下面的项目布局,以说明包装文件

├── a-subproject
│   └── build.gradle
├── settings.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar                  // 只有50KB,里面的代码用来下载真正的对应版本的Gradle distribution
│       └── gradle-wrapper.properties           // 负责配置包装器运行时行为的属性文件
├── gradlew                                     // shell脚本,用于使用包装器来执行构建
└── gradlew.bat                                 // Windows批处理脚本,用于使用包装器来执行构建

因此,您可以在包装器properties文件中找到所需的信息。例如,生成的distributionUrl:

distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-all.zip

使用wrapper批处理文件来执行构建:

$ gradlew.bat build
Downloading https://services.gradle.org/distributions/gradle-5.0-all.zip
.....................................................................................
Unzipping C:\Documents and Settings\Claudia\.gradle\wrapper\dists\gradle-5.0-all\ac27o8rbd0ic8ih41or9l32mv\gradle-5.0-all.zip to C:\Documents and Settings\Claudia\.gradle\wrapper\dists\gradle-5.0-al\ac27o8rbd0ic8ih41or9l32mv
Set executable permissions for: C:\Documents and Settings\Claudia\.gradle\wrapper\dists\gradle-5.0-all\ac27o8rbd0ic8ih41or9l32mv\gradle-5.0\bin\gradleBUILD SUCCESSFUL in 12s
1 actionable task: 1 executed

gradlew = gradle-wrapper

更新wrapper版本:

$ ./gradlew wrapper --gradle-version 6.8.2BUILD SUCCESSFUL in 4s
1 actionable task: 1 executed

GradleUserHome

在一个项目中,Gradle除了会与项目目录打交道,还会GRADLE_USER_HOME打交道。

.gradle/init.d目录用来对整个电脑的所有gradle项目做统一处理,譬如仓库替换(换成阿里云镜像,来加速下载);
.gradle/caches目录用来存储本地缓存的jar包;

Daemon

参考官方文档The Gradle Daemon

Maven中,每次构建项目都会创建一个Mvn JVM,先加载构建所需的所有jar包及一些上下文之类的东西,然后开始构建项目,构建完成后,这个JVM就被销毁了。所以每次构建都会比较耗时。

而Gradle3.0中,有了Client JVM和Daemon JVM,每次构建项目时会创建一个Client JVM,只用来接收请求和转发请求到Daemon JVM,所以它很轻量很快,当构建结束后,Client JVM会被销毁,而Daemon JVM会一直存在。若下次再来一个新的请求,Client JVM会再次被创建并转发请求到Daemon JVM,而此时构建所需的jar包或者相关的项目上下文之类的东西都在Daemon JVM中已经被缓存了,从而大大提升速度。

在默认情况下,Daemon是开启的,但Client与Daemon会有版本不兼容或者构建所需要的参数不兼容问题,如果不兼容,Client会自动创建一个新的Daemon,当然旧的Daemon在空闲3h后会自动被销毁。

若不想启用Daemon,可以在构建命令中添加启动参数-- no-daemon。但推荐所有的构建都使用Daemon,因为可加速构建。

总结

每次执行gradlew xxx命令时,gradle都会先去找机器上有没有安装这个gradlew指定版本的gradle,若没有就会先去下载,存放到GRADLE_USER_HOME中,然后查找与该版本Gradle兼容及构建所要求的参数兼容的Daemon JVM,若没找到就启动一个,否则就通过socket连接这个Daemon,然后把当前任务以及相关的上下文(eg.当前项目路径、当前环境变量)发给Daemon JVM,Daemon JVM会处理这一切。

Groovy基础

Groovy对自己的定义就是:Groovy是在 java平台上的、 具有像Python, Ruby 和 Smalltalk 语言特性的灵活动态语言, Groovy保证了这些特性像 Java语法一样被 Java开发者使用。

推荐读《Groovy in action》,这本书是基于groovy2.4写的。

Idea中"Tools" -> “Groovy Console…”,可以很方便的编写及执行Groovy代码。

动态调用与MOP

Groovy的NOP类似于java的invokedynamic,底层就是使用的发射调用。这也是Groovy的很多语法糖的底层原理。

闭包

Grrovy DSL(领域专属语言)的一些约定。

println(test(21, {2 * it}))
// 在不会引起歧义的情况下,可以省略圆括号
println test(21, {2 * it})
// 当闭包为最后一个参数时,可以放在圆括号的外面
println test(21) {2 * it}

更多约定参考Groovy Language Documentation,理解了这个文档后,就会发现gradle的脚本非常简单了!

本质上等效于

即调用Project(后面会讲到)的plugins()方法,传入一个闭包。

二、Gradle构建

Gradle的核心模型


一个项目有一个 setting.gradle、一个顶层的 build.gradle文件、每个Module 都有自己的一个 build.gradle文件。

  • setting.gradle: 这个 setting 文件定义了哪些module 应该被加入到编译过程,对于单个module 的项目可以不用需要这个文件,但是对于 multimodule 的项目我们就需要这个文件,否则gradle 不知道要加载哪些项目。这个文件的代码在初始化阶段就会被执行。

  • 顶层的build.gradle: 顶层的build.gradle文件的配置最终会被应用到所有项目中。它典型的配置如下:

    // buildscript 定义了 Android 编译工具的类路径。repositories中,jCenter是一个著名的 Maven 仓库。
    buildscript {repositories {jcenter()}dependencies {classpath 'com.android.tools.build:gradle:1.2.3'}
    }// allprojects 中定义的属性会被应用到所有 module 中,但是为了保证每个项目的独立性,我们一般不会在这里面操作太多共有的东西。
    allprojects{repositories{jcenter()}
    }
    
  • 每个Module单独的 build.gradle: 针对每个module 的配置,如果这里的定义的选项和顶层build.gradle定义的相同,后者会被覆盖。

注意:网上有人说可以把GRADLE_USER_HOME设置为maven的localRepository路径,就可以与maven本地仓库共用相同的jar包了。实际上是不可以的,因为gradle本地仓库的目录结构与maven的不一样。

Lifecycle

参考Gradle Build Lifecycle

  • 初始化阶段:Gradle支持单个和多项目构建。在初始化阶段,Gradle确定哪些项目将参与构建,并为每个项目创建一个Project实例。
  • 配置阶段:在此阶段,将配置项目对象。所有要参与到这次构建中的项目的构建脚本都被执行。即把build.gradle中的代码从头到尾执行一遍。注意:任务的闭包类型的参数中的代码是不会立马执行的,要在真正执行这个任务的时候才会执行
  • 执行阶段:Gradle确定要执行的在配置阶段创建和配置的任务子集。子集由传递给Gradle命令和当前目录的任务名称参数确定。然后,Gradle执行每个选定的任务。

Setting

对应项目根目录的setting.gradle

Project

对应每个Module单独的 build.gradle,build.gradle中一切无主的方法,都会去Project中查找。

Task

Gradle中最小的执行单元是Task,一个Task可以声明一些操作,Task之间可以进行互相依赖。
[build.gradle]

task('helloworld', {println('configure')doLast({println('Executing task')})
})


任何的gradle任务,都会经历三个阶段。在配置阶段,执行到’task('helloworld', {xxx})时,首先会创建一个名为"helloworld"的Task,然后配置该Task:对该Task运行后面的闭包(该Task为后面闭包的this,用的是Grrovy的delegate机制),怎么运行呢?当然是一行一行地执行闭包中的代码啦。注意:task方法的闭包中的doLast方法是把doLast方法中的闭包添加到这个Task的 action list 的末尾,但不真正的执行它,只有在真正执行这个Task的时候才被执行。

若执行 ./gradlew help,输出如下:

gradle help 是一个特殊任务。

若执行gradlew helloworld,输出如下:

根据Groovy的DSL,上述脚本代码可以简化为(感觉看起来很别扭,可读性比较差):

task 'helloworld' {println 'configure'doLast{println('Executing task')}
}

Gradle甚至还支持动态地创建Task
[build.gradle]

for (int i = 0; i < 5; i++) {task('task' + i) {// 若要在闭包中使用闭包外的变量,需要先捕获住它。def capturedI = i;doLast {println("executing task ${capturedI}")}}
}

Task之间可以相互依赖

task('first') {doLast { println('executing first task') }
}(0..10).each { i->task('task' + i) {if (i % 2 == 0) {dependsOn('first')}}
}

奇数号的Task不依赖first-Task

偶数号的Task依赖first-Task,所以Gradle在执行他们之前,会自动先去执行first-Task

TaskContainer

org.gradle.api.tasks.TaskContainer 是一个接口,表示装有所有Task的容器,使用 Project.getTasks()可得到该容器,所以在 build.gradle 中直接写 tasks 等效于 Project.getTasks() ,得到的是TaskContianer实例。TaskContianer接口中包含以下主要方法:

//创建task
create(name: String, configure: Closure): Task
create(name: String, type: Class): Task
create(options: Map<String, ?>, configure: Closure): Task//根据类型查找Task
withType(type: Class): TaskCollection
//根据名称查找Task
getByName(name: String): Task//任何一个task被添加加到Task容器中时
whenTaskAdded(action: Closure)

示例:

// tasks.withType(JavaCompile) = project.getTasks().withType(JavaCompile)
tasks.withType(JavaCompile) {// 闭包内是对在Task容器中找到的所有JavaCompile类型的task进行配置,可配置项都在JavaCompile类中!// 这行代码 = JavaCompile.getOptions().setEncoding("UTF-8")options.encoding = "UTF-8"// 这行代码 = JavaCompile.setSourceCompatibility(JavaVersion.VERSION_1_8)sourceCompatibility = JavaVersion.VERSION_1_8targetCompatibility = JavaVersion.VERSION_1_8
}

Hook

afterEvaluate是把方法中的闭包放在所属project的action list中,并不会真正执行它,等所属project实例的evaluate完成后才执行。
[build.gradle]

afterEvaluate {println('after evaluate')
}task('first') {println('configuring  first')doLast { println('executing first task') }
}(0..5).each { i->task('task' + i) {println("configuring task $i")if (i % 2 == 0) {doLast {println("executing task $i")}dependsOn('first')}}
}

执行gradlew task4

三、Gradle插件的编写

构建逻辑的复用

简单插件

[build.gradle]

class MyAwesomePlugin implements Plugin<Project> {// 我们接触到的绝大多数的apply参数类型都是Project@Overridevoid apply(Project project) {(0..5).each { i->project.task('task' + i) {println("configuring task $i")if (i % 2 == 0) {dependsOn('first')}def capturedI = i;doLast {println("executing task $capturedI")}}}}
}
//等价于 apply([plugin: MyAwesomePlugin]),是Projrct继承自PluginAware的apply(Map<String, ?> var1)方法。
//所以这一行就是在调用Project#apply(Map)方法,这个方法的内部,Gradle会实例化一个MyAwesomePlugin对象并调用它的apply(Project)方法。
apply plugin: MyAwesomePlugin

执行gradlew task4,效果跟之前完全一样

Script插件

参考官方文档Using Gradle Plugins

上面的简单插件方式看起来貌似没什么用,代码还是很长,其实我们可以将声明的class放在任何地方,譬如放在了网络上,则可以这样引入:

// Gradle会去下载这个脚本,然后按照与简单插件相同的方式去调用apply()
apply plugin: 'http://myserver.com/my-script'

BuildSrc插件

首先需要明白两个classpath的概念。gradle脚本(build.gradle)的classpath与java项目(CompileJava)的classpath是互相独立的。

譬如,若想要在build.gradle中使用apache-commons的StringUtils,则需要把它添加到gradle脚本的classpath中。
[build.gradle]

import org.apache.commons.lang3.StringUtils// buildscript中的声明是gradle脚本自身需要使用的资源(并非项目需要)。可以声明的资源包括依赖项、第三方插件、maven仓库地址等。
buildscript {repositories {mavenCentral()}// 添加到gradle脚本的classpathdependencies {//注意这里是'classpath',而不是'compile'classpath(group: 'org.apache.commons', name: 'commons-lang3', version: '3.9')//1.从repositories中指定的仓库中找对应jar,并添加到gradle的classpathclasspath 'mysql:mysql-connector-java:5.1.40'classpath "org.flywaydb:flyway-gradle-plugin:4.0.3"//2.将项目的 /gradleLibs/dependency-management-plugin-1.0.5.RELEASE.jar 和 /libs/commons-lang-2.6.jar 添加到gradle的classpathclasspath files('gradleLibs/dependency-management-plugin-1.0.5.RELEASE.jar', 'libs/commons-lang-2.6.jar')//3.将项目的 /gradleLibs 目录下所有jar添加到gradle的classpathclasspath fileTree(include: ['*.jar'], dir: 'gradleLibs')}
}// 因为buildscript中引入了commons-lang3包,所以在gradle的脚本中就可以使用StringUtils了
if (StringUtils.isNoneEmpty('')) {// execute builds
}

同理,若想要在build.gradle中apply自定义的plugin,也需要把它添加到gradle脚本的classpath中

gradle约定:在外层的build.gradle执行之前,若发现内部有buildSrc项目,Gradle会先编译buildSrc,然后自动把打包输出的jar(buildSrc/build/libs/buildSrc.jar)添加到外层build.gradle -> buildScript -> dependencies -> classpath 中去。所以我们在外层build.gradle中直接apply plugin: myPlugin就行了。这样就实现了把插件逻辑抽取出来,放到一个独立的buildSrc项目中。这一过程非常像Maven中的将项目install到本地仓库,然后其他项目可以很方便的引用它。具体操作如下:

  1. 先在外层根目录下新建buildSrc/src/main/java目录,然后点击Gradle工具窗的“Reload All Projects”按钮,Gradle就会自动识别出该buildSrc项目了。外层的build.gradle什么都不用做,就可以自动识别!

  2. 在buildSrc/src/main/java目录下编写自定义的插件类,eg. 路径为[buildSrc/src/main/java/MyPlugin.java]
import org.gradle.api.Plugin;
import org.gradle.api.Project;// 注意现在写的是java代码了!
public class MyPlugin implements Plugin<Project> {@Overridepublic void apply(Project project) {for (int i = 0; i < 5; i++) {// 注意task后面不要有空格project.task("task" + i);}}
}
  1. 在外层[build.gradle]中直接使用apply plugin:xxx 引入自定义的插件,然后刷新项目。
apply pugin: MyPlugin


  1. 执行自定义的任务
gradlew task2

Binary(二进制)插件

当我们的buildSrc足够成熟的时候,可以将它从我们的项目移除出来,变成一个真正的独立的插件项目,把它发布到仓库上,然后就可以在其他要使用它的项目中先手动将它添加到build.gradle -> buildScript -> dependencies -> classpath 中,然后用apply plugin: xxx引入该插件了。

四、实际插件分析

分析GitHub - android/sunflower,可以看到在build.gradle中添加了com.android.tools.build:gradle:$gradleVersion到buildScript的classpath中,然后在app/build.gradle中使用引入了该插件。


先下载com.android.tools.build:gradle:$gradleVersion这个插件的二进制jar包并解压,方便后面分析

分析得知:当Gradle执行到app/build.gradle中的apply plugin:‘com.android.applicaiton’时,就会从classpath的所有jar中找到com.android.applicaiton.properties文件(一般位置是/META-INF/gradle-plugins/xx.properties),然后根据properties的内容,得到插件实现类,再反射创建实现类的实例并调用它的apply()方法。

五、补充-java插件的war

[build.gradle]

apply plugin 'java'war{from("src/main/java/com/xx/dao") {include "*.xml"into("WEB-INF/classes/com/xx/dao")}//将依赖的jar包拷贝到lib 下task copyJars(type:Copy) {from configurations.runtimeinto 'src/main/webapp/WEB-INF/lib' // 目标位置}
}

这样设置之后,可以看到Project Settings -> modules -> Web Gradle -> Web Resource Directories 新增了一个目录,就是我们在war插件中配置的目录。

六、补充-java插件的test

[build.gradle]

apply plugin 'java'test {//使得执行gradle的test任务时,testable的Mock方法能够生效jvmArgs("-javaagent:${classpath.find { it.name.contains("testable-agent") }.absolutePath}")//(默认)开启Junit支持,还可以useTestNG()、useTestFramework()useJUnit()//模糊匹配测试类//include 'com/xx/upload/**'//精确匹配测试类//include 'com/xx/upload/UploadProcessTaskTest.class'filter({//精确匹配测试类//includeTestsMatching("com.xx.upload.UploadProcessTaskTest")//精确匹配测试方法//includeTestsMatching("com.xx.upload.UploadProcessTaskTest.getFiles")//模糊匹配测试类includeTestsMatching("com.xx.upload.*Test")})
}

七、补充-idea插件

如果你的项目使用了Gradle作为构建工具,那么你一定要使用Gradle来自动生成IDE的项目文件,无需再手动的将源代码导入到你的IDE中去了。

在build.gradle中引入idae插件:

apply plugin: "idea"

然后在命令行中输入gradle idea就可以生成idea的项目文件,直接使用idea打开生成的项目文件即可。如果在已经存在Intellij的项目文件情况下,想根据build.gradle中的配置来更新项目文件,可以输入gradle cleanIdea ideacleanIdea可以清除已有的Intellij项目文件。

Intellij主要有以下几种项目文件:

  • .ipr Intellij工程文件
  • .iml Intellij 模块文件
  • .iws Intellij 工作区文件

如果只简单的使用gradle idea生成Intellij的工程文件,其实在使用Intellij打开项目以后,我们还要做一些手工配置,比如指定JDK的版本,指定源代码管理工具等。Gradle的idea命令本质上就是生成这三个xml文件,所以Gradle提供了生成文件时的hook(钩子),让我们可以方便的做定制化,实现最大程度的自动化。这就需要自定义idea这个任务了。

//若mapper.xml不在resource包下,且使用的开发工具是IDEA,则必须添加该配置
//task mapperXmlCopy(type: Copy) {//    copy {//        from("src/main/java") {   //把src.main.java目录下的所有静态资源(例如XML)
//            include ("**/*Mapper.xml")        //标明以Mapper结尾的XML文件资源
//        }
//        into("${projectDir}/out/production/resources")   //拷贝到build后那些在resource包下的资源文件输出的目录(out/production/resources)下,测试模块的也要copy吧?
//    }
//    print "Copy Success\n"
//}apply plugin: "idea"
idea {project {//配置项目的jdk及languageLeveljdkName = '1.8'languageLevel = '1.8'}module {//默认为false,即模块的编译输出目录不继承项目的编译输出目录inheritOutputDirs = false}
}

八、补充-gradle.properties文件

第1点:build.gradle中可以自动读取gradle.properties中配置的key-value,请注意key尽量不要使用“.”和“-”,因为在groovy的语法中有特殊含义,建议使用驼峰命名。

gradle.properties

springfoxSwaggerVersion=2.1.0

build.gradle

compile(
// 使用${key}的方式读取value
"io.springfox:springfox-swagger2:${springfoxSwaggerVersion}",
// 若不引起争议,花括号可以省略
"io.springfox:springfox-swagger-ui:$springfoxSwaggerVersion",
)

第2点:若项目中使用了“io.spring.dependency-management”插件(具体使用参考我的另一篇博客Spring中依赖版本管理),可以通过gradle.properties文件覆盖jar的默认版本。注意覆盖的key必须是在spring-boot-dependencies.pom或platform-bom.pom中的内部定义的。

Gradle入门教程学习笔记相关推荐

  1. 51单片机入门教程学习笔记

    基于江科大自化协B站教学视频<51单片机入门教程-2020版 程序全程纯手打 从零开始入门> 一.单片机介绍 单片机,英文Micro Controller Unit,简称MCU 内部集成了 ...

  2. 微信小程序入门教程学习笔记

    写在前面: 作为一个刚刚入坑微信小程序的小白,以下是我在学习中的笔记,因为我真的太健忘了... 文章中可能会有错误,但是我会不断的修正的. 谢谢浏览,如有错误烦请指正 (≧∀≦)ゞ 微信官方的小程序开 ...

  3. 廖雪峰git入门教程——学习笔记

    https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000 Q1:什么是GIT? A1:分布 ...

  4. Android入门教程学习笔记

    一.四大组件 Android四大组件分别为Activity.Service.Broadcast Receiver.Content Provider Activity:用于页面展示. 通常,一个Acti ...

  5. PHPWeb开发入门体验学习笔记

    PHPWeb开发入门体验学习笔记 4 一.PHP web应用开发须知 1.入门要点 程序员三个阶段:码农(速成技能)->工程师(长期知识)->专家(研究论文) 编程三要素:声明变量(系统. ...

  6. 黑马程序员最新版JavaWeb基础教程-学习笔记

    da@黑马程序员最新版JavaWeb基础教程-学习笔记 day06-HTML&CSS HTML HTML(HyperTest Markup Language):超文本标记语言 是一门语言,所有 ...

  7. AI Studio 飞桨 零基础入门深度学习笔记6.3-手写数字识别之数据处理

    AI Studio 飞桨 零基础入门深度学习笔记6.3-手写数字识别之数据处理) 概述 前提条件 读入数据并划分数据集 扩展阅读:为什么学术界的模型总在不断精进呢? 训练样本乱序.生成批次数据 校验数 ...

  8. Python基础教程-菜鸟教程学习笔记1

    Python基础教程-菜鸟教程学习笔记1 文章目录 Python基础教程-菜鸟教程学习笔记1 前言 Python 简介 1. 第一个Python程序 2. Python 中文编码 3. 基本语法 1) ...

  9. 黑马程序员Java教程学习笔记(五)

    学习视频:https://www.bilibili.com/video/BV1Cv411372m 如侵权,请私信联系本人删除 文章目录 黑马程序员Java教程学习笔记(五) 日期时间:Date.Sim ...

  10. 大数据Hadoop教程-学习笔记01【大数据导论与Linux基础】

    视频教程:哔哩哔哩网站:黑马大数据Hadoop入门视频教程,总时长:14:22:04 教程资源:https://pan.baidu.com/s/1WYgyI3KgbzKzFD639lA-_g,提取码: ...

最新文章

  1. 利用ACS实现AAA认证
  2. mysql存储过程的返回值在哪里设置_MySQL存储过程的返回值
  3. c# mysql 汉字乱码_在C#和MySQL中存取中文字符时避免乱码的方法
  4. 【STM32 .Net MF开发板学习-28】中文显示(WPF方式)
  5. [转载] Python基础知识:构造函数中self用法
  6. 计算机网络cr什么意思,网络用语cr是什么意思
  7. python嵌套列表输出_Python列表的增删改查排嵌套特殊输出格式
  8. 联想服务器改win7系统教程,联想笔记本Win10改Win7方法分享
  9. 模电摸索日记之《模电基础》
  10. matlab实验数据拟合,利用Matlab对实验数据拟合曲线与函数方法
  11. iMX6 SoloX千兆以太网Linux PHY驱动调试
  12. 使用vcpkg安装cgal前安装yasm报错
  13. 学习笔记(97):R语言入门基础-pairs绘图
  14. 《Python自然语言处理(第二版)-Steven Bird等》学习笔记:第02章 获得文本语料和词汇资源
  15. JSP 页面访问用户验证
  16. SpringMVC拦截器
  17. iphone11各机型对比_iPhone 11系列手机买哪款好?iPhone 11系列对比评测
  18. 用ollydbg手脱ArmadilloV3.60加壳的DLL
  19. 【代码】PS2摇杆控制oled上点的移动(基于arduino uno)
  20. java.lang.ClassCastException: java.lang.Integer cannot be cast to java.math.BigD

热门文章

  1. TortoiseSVN汉化教程
  2. 十六进制 转 二进制方法汇总
  3. 电脑WindowsUDP53绕过校园网认证登陆(同时可进内外网教程)
  4. python树莓派游戏机_玩转树莓派——游戏主机模拟器
  5. 动易CMS 实现ctrl+v粘贴图片并上传、word粘贴带图片
  6. 用驱动精灵和手动更新方式安装 Arduino mega 2560 驱动失败的解决方案
  7. Spring Cloud 入门手册
  8. viewpager实现3D画廊的方法
  9. Google Code checkout v8 方法
  10. [bzoj1406][数论]密码箱