精华内容
下载资源
问答
  • 一门初级、从入门到精通的C语言C++语法教程,由毕业于清华大学的业内人士执课。从简单的HelloWorld入门程序,到深入的C语言C++核心概念,均为您娓娓道来,言之必详、听之必懂。让C语言C++编程变得简单,让C语言C++...
  • 从入门到精通,Java学习路线导航

    万次阅读 多人点赞 2019-09-09 11:00:25
    最近也有很多人来向我"请教",他们大都是一些刚入门的新手,还不了解这个行业,也不知道何学起,开始的时候非常迷茫,实在是每天回复很多人也很麻烦,所以在这里统一作个回复吧。 Java学习路线 当然,这里我只是说...

    引言

    最近也有很多人来向我"请教",他们大都是一些刚入门的新手,还不了解这个行业,也不知道从何学起,开始的时候非常迷茫,实在是每天回复很多人也很麻烦,所以在这里统一作个回复吧。

    Java学习路线

    当然,这里我只是说Java学习路线,因为自己就是学Java的,对Java理当很熟悉,对于其它方面,我也不是很了解。

    基础阶段

    首先是基础阶段,在基础阶段,我们必须掌握Java基础,Mysql数据库,Oracle数据库,JDBC,Linux基础,Java8新特性,数据结构和算法,设计模式。
    对于基础阶段,我们所要掌握的就是基础,虽然是基础,但学起来也够呛。因为是初学者,所以基础阶段会有一点难度,但只要肯坚持,这些也没什么难的。对于Linux、数据结构、算法和设计模式我们掌握一些基础就行了,也没必要学得非常透彻。

    Web基础

    基础阶段过后,我们还必须要掌握一些Web方面的知识,Html,JavaScript,JQuery和AJax,同样的,对于这些知识,我们只需掌握基础,而不需要深究,否则,你将耗费大量的时间。

    JavaWeb

    Web阶段过后,当然就是JavaWeb了,对于JavaWeb,我们需要掌握的知识有:Servlet,JSP,MVC设计模式,Cookie,Session,JavBean组件技术,EL表达式,JSTL表达式,过滤器Filter,监听器Listener,文件的上传下载,国际化。

    JavaEE

    最后是JavaEE阶段,当你学到这个阶段,你对Java的程序开发已经了如指掌。然而,自己开发的过程是艰难而复杂的,所以JavaEE阶段我们需要去学习一些非常优秀的框架,来帮助我们进行程序开发。
    对于框架,我们需要学习Struts2,Hibernate,Spring,SpringMVC,JPA,SpringData,MyBatis,Spring Boot,Spring Cloud,Dubbo。要学习的框架其实非常多,有些框架也十分有难度,我们在学习阶段只要能够熟练使用它们即可,有能力的话可以研究一下源码,看看框架的底层实现。当然,除了框架,我们还需要学习Maven,Git,Github,ActiveMQ,Jenkins等。
    在JavaEE阶段学习的东西是最多的,但如果你经历了JavaEE阶段的学习,你的编程水平将会有质的飞跃。

    其它

    这里是我在写了这篇文章几天后才加上的,有人向我反映,说数据结构和算法不学好就只能当个码农,说我在文章中指出"只需学习数据结构和算法的基础即可"是误导别人。确实,这句话是没说错,我在之前的文章中也强调过数据结构和算法的重要性,它们是程序的灵魂。然而,数据结构和算法是具有一定难度的,对于新手可能非常地不友好,新手要想彻底掌握它们是很困难的,所以我只是说在基础阶段掌握基础即可,而不需要去深究。而随着学习的深入,你的知识体系将会越来越庞大,这个时候,对于数据结构和算法可不能仅仅停留在表面了,我们应该去更加深入地研究这些东西。
    在这里也感谢大家给我提出的意见,只要大家说的在理,说的是对的,我都会去及时改正。金无足赤,人无完人,在写文章的过程中,难免会有一些不太严谨的地方,这里也欢迎大家批评指正。

    学习资源

    我知道,对于有些名词,你们可能都没有听过。没关系,按照这个路线一点一点地往下学,相信你会感受到编程的魅力所在。
    下面对整条知识路线进行一个梳理,并附上对应的学习资源,需要的小伙伴们下载就好了。

    1. 基础阶段
      Java基础——链接:链接:https://pan.baidu.com/s/17yh-M7LYSRsXmFeQKSrAww 提取码:mqf4

      Mysql数据库——链接:https://pan.baidu.com/s/1NfS6Q9fb_OMna608w9GYeg 提取码:94ns

      Oracle数据库——链接:https://pan.baidu.com/s/12uVSvaY4pbXRWy2DtIIf8w 提取码:5r8p

      JDBC——链接:https://pan.baidu.com/s/1QF9S_GpucFvoLSrQqV6d0Q 提取码:ltnh

      Linux基础——链接:https://pan.baidu.com/s/1e0ekEVjmtfNWAEO0123wDw 提取码:pwxz

      Java8新特性——链接:https://pan.baidu.com/s/103nPGgEEjbxzuy1rHRJepg 提取码:6omt

      Java数据结构和算法——链接:https://pan.baidu.com/s/1LQ__Hp7i-TR7gRa4YJQXJg 提取码:zwwd

      Java设计模式——链接:https://pan.baidu.com/s/1ik7PRwlAVgJzhSCdthdu1A 提取码:nr5e
    2. Web阶段
      JavaScript DOM——链接:https://pan.baidu.com/s/1_HtKFXeRm5xF7p2vYk_c7w 提取码:ks6g

      JQuery——链接:https://pan.baidu.com/s/1IPPNu2qsJ-MgLX7mGcVYbQ 提取码:umdr
      AJAX——链接:https://pan.baidu.com/s/1MC1iryOq9_SSV1qWYk5P5Q 提取码:78hu
    3. JavaWeb
      JavaWeb阶段——链接:https://pan.baidu.com/s/1EJYIgAuAHhaJLv8Pp-QjgA 提取码:584f
    4. JavaEE
      JavaEE阶段全套视频——链接:https://pan.baidu.com/s/1rJ5MyvFMuN_cbDVbw-2B3A 提取码:mdlf

    课件及源代码

    文章发布过后,有很多人来问我有没有源码和课件,这里当然是有的,为了方便大家,我就把源码和课件链接放在这里,也不用劳烦大家一直问了。
    链接:https://pan.baidu.com/s/18fr2-Hy-0aoRr9wDIl1zbg 提取码:2zxp

    关于Java学习路线上的所有资源都在这里了,欢迎大家下载!

    马上就要到中秋佳节了,在这里也提前祝贺各位中秋节快乐,身体健康,阖家欢乐!

    2021年03月09日更新

    因为百度网盘自身的一些原因,导致这些资源经常出现失效的现象,而且资源数目比较多,维护起来比较麻烦,所以我特地将所有资源进行了一个整合,上面的所有学习资源都在这个链接里面:

    链接:https://pan.baidu.com/s/1Qi6IU7Gf4CVCh_cPsuRgrw
    提取码:z90d

    展开全文
  • Spring Cloud 从入门到精通

    万次阅读 多人点赞 2018-07-03 02:45:08
    我们可以注意,没有写任何的配置文件,更没有显示使用任何容器,只需要启动 Main 方法即可开启 Web 服务,从而访问 HelloController 类里定义的路由地址。 这就是 Spring Boot 的强大之处,它默认集成了 ...

    Spring Cloud 是一套完整的微服务解决方案,基于 Spring Boot 框架,准确的说,它不是一个框架,而是一个大的容器,它将市面上较好的微服务框架集成进来,从而简化了开发者的代码量。

    本课程由浅入深带领大家一步步攻克 Spring Cloud 各大模块,接着通过一个实例带领大家了解大型分布式微服务架构的搭建过程,最后深入源码加深对它的了解。

    本课程共分为四个部分:

    第一部分(第1-3课),初识 Spring Boot,掌握 Spring Boot 基础知识,为后续入门 Spring Cloud 打好基础 。

    第二部分(第4-13课),Spring Cloud 入门篇,主要介绍 Spring Cloud 常用模块,包括服务发现、服务注册、配置中心、链路追踪、异常处理等。

    第三部分(第14-18课),Spring Cloud 进阶篇,介绍大型分布式系统中事务处理、线程安全等问题,并以一个实例项目手把手教大家搭建完整的微服务系统。

    第四部分(第19-20课),Spring Cloud 高级篇,解析 Spring Cloud 源码,并讲解如何部署基于 Spring Cloud 的大型分布式系统。

    点击购买,可阅读课程全文

    作者介绍

    李熠,从事 Java 后端开发 6 年,现任职某大型互联网公司,担任 Java 高级开发工程师,CSDN 博客专家,全栈工程师。

    课程内容

    导读:什么是 Spring Cloud 及应用现状

    Spring Cloud 是什么?

    在学习本课程之前,读者有必要先了解一下 Spring Cloud。

    Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的开发便利性简化了分布式系统的开发,比如服务发现、服务网关、服务路由、链路追踪等。Spring Cloud 并不重复造轮子,而是将市面上开发得比较好的模块集成进去,进行封装,从而减少了各模块的开发成本。换句话说:Spring Cloud 提供了构建分布式系统所需的“全家桶”。

    Spring Cloud 现状

    目前,国内使用 Spring Cloud 技术的公司并不多见,不是因为 Spring Cloud 不好,主要原因有以下几点:

    1. Spring Cloud 中文文档较少,出现问题网上没有太多的解决方案。
    2. 国内创业型公司技术老大大多是阿里系员工,而阿里系多采用 Dubbo 来构建微服务架构。
    3. 大型公司基本都有自己的分布式解决方案,而中小型公司的架构很多用不上微服务,所以没有采用 Spring Cloud 的必要性。

    但是,微服务架构是一个趋势,而 Spring Cloud 是微服务解决方案的佼佼者,这也是作者写本系列课程的意义所在。

    Spring Cloud 优缺点

    其主要优点有:

    1. 集大成者,Spring Cloud 包含了微服务架构的方方面面。
    2. 约定优于配置,基于注解,没有配置文件。
    3. 轻量级组件,Spring Cloud 整合的组件大多比较轻量级,且都是各自领域的佼佼者。
    4. 开发简便,Spring Cloud 对各个组件进行了大量的封装,从而简化了开发。
    5. 开发灵活,Spring Cloud 的组件都是解耦的,开发人员可以灵活按需选择组件。

    接下来,我们看下它的缺点:

    1. 项目结构复杂,每一个组件或者每一个服务都需要创建一个项目。
    2. 部署门槛高,项目部署需要配合 Docker 等容器技术进行集群部署,而要想深入了解 Docker,学习成本高。

    Spring Cloud 的优势是显而易见的。因此对于想研究微服务架构的同学来说,学习 Spring Cloud 是一个不错的选择。

    Spring Cloud 和 Dubbo 对比

    Dubbo 只是实现了服务治理,而 Spring Cloud 实现了微服务架构的方方面面,服务治理只是其中的一个方面。下面通过一张图对其进行比较:

    这里写图片描述

    (图片引自:程序猿DD,作者:翟永超)

    可以看出,Spring Cloud 比较全面,而 Dubbo 由于只实现了服务治理,需要集成其他模块,需要单独引入,增加了学习成本和集成成本。

    Spring Cloud 学习

    Spring Cloud 基于 Spring Boot,因此在研究 Spring Cloud 之前,本课程会首先介绍 Spring Boot 的用法,方便后续 Spring Cloud 的学习。

    本课程不会讲解 Spring MVC 的用法,因此学习本课程需要读者对 Spring 及 Spring MVC 有过研究。

    本课程共分为四个部分:

    • 第一部分初识 Spring Boot,掌握 Spring Boot 基础知识,为后续入门 Spring Cloud 打好基础 。

    • 第二部分 Spring Cloud 入门篇,主要介绍 Spring Cloud 常用模块,包括服务发现、服务注册、配置中心、链路追踪、异常处理等。

    • 第三部分 Spring Cloud 进阶篇,介绍大型分布式系统中事务处理、线程安全等问题,并以一个实例项目手把手教大家搭建完整的微服务系统。

    • 第四部分 Spring Cloud 高级篇,解析 Spring Cloud 源码,并讲解如何部署基于 Spring Cloud 的大型分布式系统。

    本课程的所有示例代码均可在:https://github.com/lynnlovemin/SpringCloudLesson 下载。

    第01课:Spring Boot 入门

    什么是 Spring Boot

    Spring Boot 是由 Pivotal 团队提供的基于 Spring 的全新框架,其设计目的是为了简化 Spring 应用的搭建和开发过程。该框架遵循“约定大于配置”原则,采用特定的方式进行配置,从而使开发者无需定义大量的 XML 配置。通过这种方式,Spring Boot 致力于在蓬勃发展的快速应用开发领域成为领导者。

    Spring Boot 并不重复造轮子,而且在原有 Spring 的框架基础上封装了一层,并且它集成了一些类库,用于简化开发。换句话说,Spring Boot 就是一个大容器。

    下面几张图展示了官网上提供的 Spring Boot 所集成的所有类库:

    这里写图片描述

    这里写图片描述

    这里写图片描述

    Spring Boot 官方推荐使用 Maven 或 Gradle 来构建项目,本教程采用 Maven。

    第一个 Spring Boot 项目

    大多数教程都是以 Hello World 入门,本教程也不例外,接下来,我们就来搭建一个最简单的 Spring Boot 项目。

    1.创建一个 Maven 工程,请看下图:

    这里写图片描述

    2.在 pom.xml 加入 Spring Boot 依赖:

    <parent>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-parent</artifactId>    <version>2.1.3.RELEASE</version></parent><dependencies>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-web</artifactId>    </dependency></dependencies>

    3.创建应用程序启动类 DemoApplication,并编写以下代码:

    import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class DemoApplication {    public static void main(String[] args) {        SpringApplication.run(DemoApplication.class, args);    }}

    4.创建一个 Controller 类 HelloController,用以测试我们的第一个基于 Spring Boot 的 Web 应用:

    import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class HelloController {    @RequestMapping("hello")    String hello() {        return "Hello World!";    }}

    5.运行 DemoApplication 类中的 main 方法,看到下图所示内容说明应用启动成功:

    enter image description here

    6.浏览器访问:http://localhost:8080/hello,则会看到下图所示界面:

    这里写图片描述

    我们可以注意到,没有写任何的配置文件,更没有显示使用任何容器,只需要启动 Main 方法即可开启 Web 服务,从而访问到 HelloController 类里定义的路由地址。

    这就是 Spring Boot 的强大之处,它默认集成了 Tomcat 容器,通过 Main 方法编写的 SpringApplication.run 方法即可启动内置 Tomcat。

    它是如何启动的,内部又是如何运行的呢?具体原理我将在《第03课:Spring Boot 启动原理》一节中具体分析。

    在上面的示例中,我们没有定义应用程序启动端口,可以看到控制台,它开启了 8080 端口,这是 Spring Boot 默认的启动端口。Spring Boot 提供了默认的配置,我们也可以改变这些配置,具体方法将在后面介绍。

    我们在启动类里加入 @SpringBootApplication 注解,则这个类就是整个应用程序的启动类。如果不加这个注解,启动程序将会报错,读者可以尝试一下。

    Spring Boot 还有一个特点是利用注解代码繁琐的 XML 配置,整个应用程序只有一个入口配置文件,那就是 application.yml 或 application.properties。接下来,我将介绍其配置文件的用法。

    properties 和 yaml

    在前面的示例代码中,我们并没有看到该配置文件,那是因为 Spring Boot 对每个配置项都有默认值。当然,我们也可以添加配置文件,用以覆盖其默认值,这里以 .properties 文件为例,首先在 resources 下新建一个名为 application.properties(注意:文件名必须是 application)的文件,键入内容为:

    server.port=8081server.servlet.context-path=/api

    并且启动 Main 方法,这时程序请求地址则变成了:http://localhost:8081/api/hello。

    Spring Boot 支持 properties 和 yaml 两种格式的文件,文件名分别对应 application.properties 和 application.yml,下面贴出 yaml 文件格式供大家参考:

    server:  port: 8080  servlet:    context-path: /api

    可以看出 properties 是以逗号隔开,而 yaml 则换行+ 两个空格 隔开,这里需要注意的是冒号后面必须空格,否则会报错。yaml 文件格式更清晰,更易读,这里作者建议大家都采用 yaml 文件来配置。

    以上示例只是小试牛刀,更多的配置将在后面的课程中讲解。

    本教程的所有配置均采用 yaml 文件。

    打包、运行

    Spring Boot 打包分为 war 和 jar 两个格式,下面将分别演示如何构建这两种格式的启动包。

    在 pom.xml 加入如下配置:

    <packaging>war</packaging><build>    <finalName>api</finalName>    <resources>        <resource>            <directory>src/main/resources</directory>            <filtering>true</filtering>        </resource>    </resources>    <plugins>        <plugin>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-maven-plugin</artifactId>        </plugin>        <plugin>            <artifactId>maven-resources-plugin</artifactId>            <version>2.5</version>            <configuration>                <encoding>UTF-8</encoding>            </configuration>        </plugin>        <plugin>            <groupId>org.apache.maven.plugins</groupId>            <artifactId>maven-surefire-plugin</artifactId>            <version>2.18.1</version>            <configuration>                <skipTests>true</skipTests>            </configuration>        </plugin>    </plugins></build>

    这个时候运行 mvn package 就会生成 war 包,然后放到 Tomcat 当中就能启动,但是我们单纯这样配置在 Tomcat 是不能成功运行的,会报错,需要通过编码指定 Tomcat 容器启动,修改 DemoApplication 类:

    import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.builder.SpringApplicationBuilder;import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;@SpringBootApplicationpublic class DemoApplication extends SpringBootServletInitializer {    public static void main(String[] args) {        SpringApplication.run(DemoApplication.class, args);    }    @Override    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {        return application.sources(DemoApplication.class);    }}

    这时再打包放到 Tomcat,启动就不会报错了。

    在上述代码中,DemoApplication 类继承了 SpringBootServletInitializer,并重写 configure 方法,目的是告诉外部 Tomcat,启动时执行该方法,然后在该方法体内指定应用程序入口为 DemoApplication 类,如果通过外部 Tomcat 启动 Spring Boot 应用,则其配置文件设置的端口和 contextPath 是无效的。这时,应用程序的启动端口即是 Tomcat 的启动端口,contextPath 和 war 包的文件名相同。

    接下来我们继续看如果达成 jar 包,在 pom.xml 加入如下配置:

    <!-- 需要将包类型改为 jar 包 --><packaging>jar</packaging><build>    <finalName>api</finalName>    <resources>        <resource>            <directory>src/main/resources</directory>            <filtering>true</filtering>        </resource>    </resources>    <plugins>        <plugin>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-maven-plugin</artifactId>            <configuration>                <fork>true</fork>                <!-- 指定 Main 方法所在类 -->                <mainClass>com.lynn.DemoApplication</mainClass>            </configuration>            <executions>                <execution>                    <goals>                        <goal>repackage</goal>                    </goals>                </execution>            </executions>        </plugin>        <plugin>            <artifactId>maven-resources-plugin</artifactId>            <version>2.5</version>            <configuration>                <encoding>UTF-8</encoding>                <useDefaultDelimiters>true</useDefaultDelimiters>            </configuration>        </plugin>        <plugin>            <groupId>org.apache.maven.plugins</groupId>            <artifactId>maven-surefire-plugin</artifactId>            <version>2.18.1</version>            <configuration>                <skipTests>true</skipTests>            </configuration>        </plugin>        <plugin>            <groupId>org.apache.maven.plugins</groupId>            <artifactId>maven-compiler-plugin</artifactId>            <version>2.3.2</version>            <configuration>                <source>1.8</source>                <target>1.8</target>            </configuration>        </plugin>    </plugins></build>

    然后通过 mvn package 打包,最后通过 java 命令启动:

    java -jar api.jar

    如果是 Linux 服务器,上述命令是前台进程,点击 Ctrl+C 进程就会停止,可以考虑用 nohup 命令开启守护进程,这样应用程序才不会自动停止。

    这样,最简单的 Spring Boot 就完成了,但是对于一个大型项目,这是远远不够的,Spring Boot 的详细操作可以参照官网

    下面展示一个最基础的企业级 Spring Boot 项目的结构:

    这里写图片描述

    其中,Application.java 是程序的启动类,Startup.java 是程序启动完成前执行的类,WebConfig.java 是配置类,所有 Bean 注入、配置、拦截器注入等都放在这个类里面。

    以上实例只是最简单的 Spring Boot 项目入门实例,后面会深入研究 Spring Boot。

    第02课:Spring Boot 进阶

    上一篇带领大家初步了解了如何使用 Spring Boot 搭建框架,通过 Spring Boot 和传统的 SpringMVC 架构的对比,我们清晰地发现 Spring Boot 的好处,它使我们的代码更加简单,结构更加清晰。

    从这一篇开始,我将带领大家更加深入的认识 Spring Boot,将 Spring Boot 涉及到东西进行拆解,从而了解 Spring Boot 的方方面面。学完本文后,读者可以基于 Spring Boot 搭建更加复杂的系统框架。

    我们知道,Spring Boot 是一个大容器,它将很多第三方框架都进行了集成,我们在实际项目中用到哪个模块,再引入哪个模块。比如我们项目中的持久化框架用 MyBatis,则在 pom.xml 添加如下依赖:

    <dependency>            <groupId>org.mybatis.spring.boot</groupId>            <artifactId>mybatis-spring-boot-starter</artifactId>            <version>1.1.1</version>        </dependency>        <dependency>            <groupId>mysql</groupId>            <artifactId>mysql-connector-java</artifactId>            <version>5.1.40</version>        </dependency>

    yaml/properties 文件

    我们知道整个 Spring Boot 项目只有一个配置文件,那就是 application.yml,Spring Boot 在启动时,就会从 application.yml 中读取配置信息,并加载到内存中。上一篇我们只是粗略的列举了几个配置项,其实 Spring Boot 的配置项是很多的,本文我们将学习在实际项目中常用的配置项(注:为了方便说明,配置项均以 properties 文件的格式写出,后续的实际配置都会写成 yaml 格式)。

    配置项 说明 举例
    server.port 应用程序启动端口 server.port=8080,定义应用程序启动端口为 8080
    server.servlet.context-path 应用程序上下文 server.servlet.context-path=/api,则访问地址为:http://ip:port/api
    spring.servlet.multipart.maxFileSize 最大文件上传大小,-1为不限制 spring.servlet.multipart.maxFileSize=-1
    spring.jpa.database 数据库类型 spring.jpa.database=MYSQL,指定数据库为mysql
    spring.jpa.properties.hibernate.dialect hql方言 spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
    spring.datasource.url 数据库连接字符串 spring.datasource.url=jdbc:mysql://localhost:3306/database?useUnicode=true&characterEncoding=UTF-8&useSSL=true
    spring.datasource.username 数据库用户名 spring.datasource.username=root
    spring.datasource.password 数据库密码 spring.datasource.password=root
    spring.datasource.driverClassName 数据库驱动 spring.datasource.driverClassName=com.mysql.jdbc.Driver
    spring.jpa.showSql 控制台是否打印 SQL 语句 spring.jpa.showSql=true

    下面是我参与的某个项目的 application.yml 配置文件内容:

    server:  port: 8080  servlet:    context-path: /api  tomcat:    basedir: /data/tmp    max-threads: 1000    min-spare-threads: 50  connection-timeout: 5000spring:  profiles:    active: dev  servlet:    multipart:      maxFileSize: -1  datasource:    url: jdbc:mysql://localhost:3306/database?useUnicode=true&characterEncoding=UTF-8&useSSL=true    username: root    password: root    driverClassName: com.mysql.jdbc.Driver  jpa:    database: MYSQL    showSql: true    hibernate:      namingStrategy: org.hibernate.cfg.ImprovedNamingStrategy    properties:      hibernate:        dialect: org.hibernate.dialect.MySQL5Dialectmybatis:  configuration:     #配置项:开启下划线到驼峰的自动转换. 作用:将数据库字段根据驼峰规则自动注入到对象属性     map-underscore-to-camel-case: true

    以上列举了常用的配置项,所有配置项信息都可以在官网中找到,本课程就不一一列举了。

    多环境配置

    在一个企业级系统中,我们可能会遇到这样一个问题:开发时使用开发环境,测试时使用测试环境,上线时使用生产环境。每个环境的配置都可能不一样,比如开发环境的数据库是本地地址,而测试环境的数据库是测试地址。那我们在打包的时候如何生成不同环境的包呢?

    这里的解决方案有很多:

    1. 每次编译之前手动把所有配置信息修改成当前运行的环境信息。这种方式导致每次都需要修改,相当麻烦,也容易出错。
    2. 利用 Maven,在 pom.xml 里配置多个环境,每次编译之前将 settings.xml 里面修改成当前要编译的环境 ID。这种方式会事先设置好所有环境,缺点就是每次也需要手动指定环境,如果环境指定错误,发布时是不知道的。
    3. 第三种方案就是本文重点介绍的,也是作者强烈推荐的方式。

    首先,创建 application.yml 文件,在里面添加如下内容:

    spring:  profiles:    active: dev

    含义是指定当前项目的默认环境为 dev,即项目启动时如果不指定任何环境,Spring Boot 会自动从 dev 环境文件中读取配置信息。我们可以将不同环境都共同的配置信息写到这个文件中。

    然后创建多环境配置文件,文件名的格式为:application-{profile}.yml,其中,{profile} 替换为环境名字,如 application-dev.yml,我们可以在其中添加当前环境的配置信息,如添加数据源:

    spring:  datasource:    url: jdbc:mysql://localhost:3306/database?useUnicode=true&characterEncoding=UTF-8&useSSL=true    username: root    password: root    driverClassName: com.mysql.jdbc.Driver

    这样,我们就实现了多环境的配置,每次编译打包我们无需修改任何东西,编译为 jar 文件后,运行命令:

    java -jar api.jar --spring.profiles.active=dev

    其中 --spring.profiles.active 就是我们要指定的环境。

    常用注解

    我们知道,Spring Boot 主要采用注解的方式,在《第01课:Spring Boot 入门》一节的入门实例中,我们也用到了一些注解。

    本文,我将详细介绍在实际项目中常用的注解。

    @SpringBootApplication

    我们可以注意到 Spring Boot 支持 main 方法启动,在我们需要启动的主类中加入此注解,告诉 Spring Boot,这个类是程序的入口。如:

    @SpringBootApplicationpublic class DemoApplication {    public static void main(String[] args) {        SpringApplication.run(DemoApplication.class, args);    }}

    如果不加这个注解,程序是无法启动的。

    我们查看下 SpringBootApplication 的源码,源码如下:

    @Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(excludeFilters = {        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })public @interface SpringBootApplication {    /**     * Exclude specific auto-configuration classes such that they will never be applied.     * @return the classes to exclude     */    @AliasFor(annotation = EnableAutoConfiguration.class, attribute = "exclude")    Class<?>[] exclude() default {};    /**     * Exclude specific auto-configuration class names such that they will never be     * applied.     * @return the class names to exclude     * @since 1.3.0     */    @AliasFor(annotation = EnableAutoConfiguration.class, attribute = "excludeName")    String[] excludeName() default {};    /**     * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}     * for a type-safe alternative to String-based package names.     * @return base packages to scan     * @since 1.3.0     */    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")    String[] scanBasePackages() default {};    /**     * Type-safe alternative to {@link #scanBasePackages} for specifying the packages to     * scan for annotated components. The package of each class specified will be scanned.     * <p>     * Consider creating a special no-op marker class or interface in each package that     * serves no purpose other than being referenced by this attribute.     * @return base packages to scan     * @since 1.3.0     */    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")    Class<?>[] scanBasePackageClasses() default {};}

    在这个注解类上有 3 个注解,如下:

    @SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(excludeFilters = {        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

    因此,我们可以用这三个注解代替 SpringBootApplication,如:

    @SpringBootConfiguration@EnableAutoConfiguration@ComponentScanpublic class Application {    public static void main(String[] args) {        SpringApplication.run(Application.class, args);    }}

    其中,SpringBootConfiguration 表示 Spring Boot 的配置注解,EnableAutoConfiguration 表示自动配置,ComponentScan 表示 Spring Boot 扫描 Bean 的规则,比如扫描哪些包。

    @Configuration

    加入了这个注解的类被认为是 Spring Boot 的配置类,我们知道可以在 application.yml 设置一些配置,也可以通过代码设置配置。

    如果我们要通过代码设置配置,就必须在这个类上标注 Configuration 注解。如下代码:

    @Configurationpublic class WebConfig extends WebMvcConfigurationSupport{    @Override    protected void addInterceptors(InterceptorRegistry registry) {        super.addInterceptors(registry);        registry.addInterceptor(new ApiInterceptor());    }}

    不过 Spring Boot 官方推荐 Spring Boot 项目用 SpringBootConfiguration 来代替 Configuration。

    @Bean

    这个注解是方法级别上的注解,主要添加在 @Configuration@SpringBootConfiguration 注解的类,有时也可以添加在 @Component 注解的类。它的作用是定义一个Bean。

    请看下面代码:

        @Bean    public ApiInterceptor interceptor(){        return new ApiInterceptor();    }

    那么,我们可以在 ApiInterceptor 里面注入其他 Bean,也可以在其他 Bean 注入这个类。

    @Value

    通常情况下,我们需要定义一些全局变量,都会想到的方法是定义一个 public static 变量,在需要时调用,是否有其他更好的方案呢?答案是肯定的。下面请看代码:

        @Value("${server.port}")    String port;    @RequestMapping("/hello")    public String home(String name) {        return "hi "+name+",i am from port:" +port;    }

    其中,server.port 就是我们在 application.yml 里面定义的属性,我们可以自定义任意属性名,通过 @Value 注解就可以将其取出来。

    它的好处不言而喻:

    1. 定义在配置文件里,变量发生变化,无需修改代码。
    2. 变量交给Spring来管理,性能更好。

    注: 本课程默认针对于对 SpringMVC 有所了解的读者,Spring Boot 本身基于 Spring 开发的,因此,本文不再讲解其他 Spring 的注解。

    注入任何类

    本节通过一个实际的例子来讲解如何注入一个普通类,并且说明这样做的好处。

    假设一个需求是这样的:项目要求使用阿里云的 OSS 进行文件上传。

    我们知道,一个项目一般会分为开发环境、测试环境和生产环境。OSS 文件上传一般有如下几个参数:appKey、appSecret、bucket、endpoint 等。不同环境的参数都可能不一样,这样便于区分。按照传统的做法,我们在代码里设置这些参数,这样做的话,每次发布不同的环境包都需要手动修改代码。

    这个时候,我们就可以考虑将这些参数定义到配置文件里面,通过前面提到的 @Value 注解取出来,再通过 @Bean 将其定义为一个 Bean,这时我们只需要在需要使用的地方注入该 Bean 即可。

    首先在 application.yml 加入如下内容:

    oss:  appKey: 1  appSecret: 1  bucket: lynn  endPoint: https://www.aliyun.com

    其次创建一个普通类:

    public class Aliyun {    private String appKey;    private String appSecret;    private String bucket;    private String endPoint;    public static class Builder{        private String appKey;        private String appSecret;        private String bucket;        private String endPoint;        public Builder setAppKey(String appKey){            this.appKey = appKey;            return this;        }        public Builder setAppSecret(String appSecret){            this.appSecret = appSecret;            return this;        }        public Builder setBucket(String bucket){            this.bucket = bucket;            return this;        }        public Builder setEndPoint(String endPoint){            this.endPoint = endPoint;            return this;        }        public Aliyun build(){            return new Aliyun(this);        }    }    public static Builder options(){        return new Aliyun.Builder();    }    private Aliyun(Builder builder){        this.appKey = builder.appKey;        this.appSecret = builder.appSecret;        this.bucket = builder.bucket;        this.endPoint = builder.endPoint;    }    public String getAppKey() {        return appKey;    }    public String getAppSecret() {        return appSecret;    }    public String getBucket() {        return bucket;    }    public String getEndPoint() {        return endPoint;    }}

    然后在 @SpringBootConfiguration 注解的类添加如下代码:

        @Value("${oss.appKey}")    private String appKey;    @Value("${oss.appSecret}")    private String appSecret;    @Value("${oss.bucket}")    private String bucket;    @Value("${oss.endPoint}")    private String endPoint;    @Bean    public Aliyun aliyun(){        return Aliyun.options()                .setAppKey(appKey)                .setAppSecret(appSecret)                .setBucket(bucket)                .setEndPoint(endPoint)                .build();    }

    最后在需要的地方注入这个 Bean 即可:

        @Autowired    private Aliyun aliyun;

    以上代码其实并不完美,如果增加一个属性,就需要在 Aliyun 类新增一个字段,还需要在 Configuration 类里注入它,扩展性不好,那么我们有没有更好的方式呢?

    答案是肯定的,我们可以利用 ConfigurationProperties 注解更轻松地将配置信息注入到实体中。

    1.创建实体类 AliyunAuto,并编写以下代码:

    import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.stereotype.Component;@Component@ConfigurationProperties(prefix = "oss")public class AliyunAuto {    private String appKey;    private String appSecret;    private String bucket;    private String endPoint;    public String getAppKey() {        return appKey;    }    public void setAppKey(String appKey) {        this.appKey = appKey;    }    public String getAppSecret() {        return appSecret;    }    public void setAppSecret(String appSecret) {        this.appSecret = appSecret;    }    public String getBucket() {        return bucket;    }    public void setBucket(String bucket) {        this.bucket = bucket;    }    public String getEndPoint() {        return endPoint;    }    public void setEndPoint(String endPoint) {        this.endPoint = endPoint;    }    @Override    public String toString() {        return "AliyunAuto{" +                "appKey='" + appKey + '\'' +                ", appSecret='" + appSecret + '\'' +                ", bucket='" + bucket + '\'' +                ", endPoint='" + endPoint + '\'' +                '}';    }}

    其中,ConfigurationProperties 指定配置文件的前缀属性,实体具体字段和配置文件字段名一致,如在上述代码中,字段为 appKey,则自动获取 oss.appKey 的值,将其映射到 appKey 字段中,这样就完成了自动的注入。如果我们增加一个属性,则只需要修改 Bean 和配置文件即可,不用显示注入。

    拦截器

    我们在提供 API 的时候,经常需要对 API 进行统一的拦截,比如进行接口的安全性校验。

    本节,我会讲解 Spring Boot 是如何进行拦截器设置的,请看接下来的代码。

    创建一个拦截器类:ApiInterceptor,并实现 HandlerInterceptor 接口:

    public class ApiInterceptor implements HandlerInterceptor {    //请求之前    @Override    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {        System.out.println("进入拦截器");        return true;    }    //请求时    @Override    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {    }    //请求完成    @Override    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {    }}

    @SpringBootConfiguration 注解的类继承 WebMvcConfigurationSupport 类,并重写 addInterceptors 方法,将 ApiInterceptor 拦截器类添加进去,代码如下:

    @SpringBootConfigurationpublic class WebConfig extends WebMvcConfigurationSupport{    @Override    protected void addInterceptors(InterceptorRegistry registry) {        super.addInterceptors(registry);        registry.addInterceptor(new ApiInterceptor());    }}

    异常处理

    我们在 Controller 里提供接口,通常需要捕捉异常,并进行友好提示,否则一旦出错,界面上就会显示报错信息,给用户一种不好的体验。最简单的做法就是每个方法都使用 try catch 进行捕捉,报错后,则在 catch 里面设置友好的报错提示。如果方法很多,每个都需要 try catch,代码会显得臃肿,写起来也比较麻烦。

    我们可不可以提供一个公共的入口进行统一的异常处理呢?当然可以。方法很多,这里我们通过 Spring 的 AOP 特性就可以很方便的实现异常的统一处理。实现方法很简单,只需要在 Controller 类添加以下代码即可。

        @ExceptionHandler    public String doError(Exception ex) throws Exception{        ex.printStackTrace();        return ex.getMessage();    }

    其中,在 doError 方法上加入 @ExceptionHandler 注解即可,这样,接口发生异常会自动调用该方法。

    这样,我们无需每个方法都添加 try catch,一旦报错,则会执行 handleThrowing 方法。

    优雅的输入合法性校验

    为了接口的健壮性,我们通常除了客户端进行输入合法性校验外,在 Controller 的方法里,我们也需要对参数进行合法性校验,传统的做法是每个方法的参数都做一遍判断,这种方式和上一节讲的异常处理一个道理,不太优雅,也不易维护。

    其实,SpringMVC 提供了验证接口,下面请看代码:

    @GetMapping("authorize")public void authorize(@Valid AuthorizeIn authorize, BindingResult ret){    if(result.hasFieldErrors()){            List<FieldError> errorList = result.getFieldErrors();            //通过断言抛出参数不合法的异常            errorList.stream().forEach(item -> Assert.isTrue(false,item.getDefaultMessage()));        }}public class AuthorizeIn extends BaseModel{    @NotBlank(message = "缺少response_type参数")    private String responseType;    @NotBlank(message = "缺少client_id参数")    private String ClientId;    private String state;    @NotBlank(message = "缺少redirect_uri参数")    private String redirectUri;    public String getResponseType() {        return responseType;    }    public void setResponseType(String responseType) {        this.responseType = responseType;    }    public String getClientId() {        return ClientId;    }    public void setClientId(String clientId) {        ClientId = clientId;    }    public String getState() {        return state;    }    public void setState(String state) {        this.state = state;    }    public String getRedirectUri() {        return redirectUri;    }    public void setRedirectUri(String redirectUri) {        this.redirectUri = redirectUri;    }}

    我们再把验证的代码单独封装成方法:

    protected void validate(BindingResult result){        if(result.hasFieldErrors()){            List<FieldError> errorList = result.getFieldErrors();            errorList.stream().forEach(item -> Assert.isTrue(false,item.getDefaultMessage()));        }    }

    这样每次参数校验只需要调用 validate 方法就行了,我们可以看到代码的可读性也大大提高了。

    接口版本控制

    一个系统上线后会不断迭代更新,需求也会不断变化,有可能接口的参数也会发生变化,如果在原有的参数上直接修改,可能会影响线上系统的正常运行,这时我们就需要设置不同的版本,这样即使参数发生变化,由于老版本没有变化,因此不会影响上线系统的运行。

    一般我们可以在地址上带上版本号,也可以在参数上带上版本号,还可以再 header 里带上版本号,这里我们在地址上带上版本号,大致的地址如:http://api.example.com/v1/test,其中,v1 即代表的是版本号。具体做法请看代码:

    @Target({ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Mappingpublic @interface ApiVersion {    /**     * 标识版本号     * @return     */    int value();}public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {    // 路径中版本的前缀, 这里用 /v[1-9]/的形式    private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile("v(\\d+)/");    private int apiVersion;    public ApiVersionCondition(int apiVersion){        this.apiVersion = apiVersion;    }    @Override    public ApiVersionCondition combine(ApiVersionCondition other) {        // 采用最后定义优先原则,则方法上的定义覆盖类上面的定义        return new ApiVersionCondition(other.getApiVersion());    }    @Override    public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {        Matcher m = VERSION_PREFIX_PATTERN.matcher(request.getRequestURI());        if(m.find()){            Integer version = Integer.valueOf(m.group(1));            if(version >= this.apiVersion)            {                return this;            }        }        return null;    }    @Override    public int compareTo(ApiVersionCondition other, HttpServletRequest request) {        // 优先匹配最新的版本号        return other.getApiVersion() - this.apiVersion;    }    public int getApiVersion() {        return apiVersion;    }}public class CustomRequestMappingHandlerMapping extends        RequestMappingHandlerMapping {    @Override    protected RequestCondition<ApiVersionCondition> getCustomTypeCondition(Class<?> handlerType) {        ApiVersion apiVersion = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);        return createCondition(apiVersion);    }    @Override    protected RequestCondition<ApiVersionCondition> getCustomMethodCondition(Method method) {        ApiVersion apiVersion = AnnotationUtils.findAnnotation(method, ApiVersion.class);        return createCondition(apiVersion);    }    private RequestCondition<ApiVersionCondition> createCondition(ApiVersion apiVersion) {        return apiVersion == null ? null : new ApiVersionCondition(apiVersion.value());    }}@SpringBootConfigurationpublic class WebConfig extends WebMvcConfigurationSupport {    @Bean    public AuthInterceptor interceptor(){        return new AuthInterceptor();    }    @Override    public void addInterceptors(InterceptorRegistry registry) {        registry.addInterceptor(new AuthInterceptor());    }    @Override    @Bean    public RequestMappingHandlerMapping requestMappingHandlerMapping() {        RequestMappingHandlerMapping handlerMapping = new CustomRequestMappingHandlerMapping();        handlerMapping.setOrder(0);        handlerMapping.setInterceptors(getInterceptors());        return handlerMapping;    }}

    Controller 类的接口定义如下:

    @ApiVersion(1)@RequestMapping("{version}/dd")public class HelloController{}

    这样我们就实现了版本控制,如果增加了一个版本,则创建一个新的 Controller,方法名一致,ApiVersion 设置为2,则地址中 v1 会找到 ApiVersion 为1的方法,v2 会找到 ApiVersion 为2的方法。

    自定义 JSON 解析

    Spring Boot 中 RestController 返回的字符串默认使用 Jackson 引擎,它也提供了工厂类,我们可以自定义 JSON 引擎,本节实例我们将 JSON 引擎替换为 fastJSON,首先需要引入 fastJSON:

    <dependency>            <groupId>com.alibaba</groupId>            <artifactId>fastjson</artifactId>            <version>${fastjson.version}</version>        </dependency>

    其次,在 WebConfig 类重写 configureMessageConverters 方法:

    @Override    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {        super.configureMessageConverters(converters);        /*        1.需要先定义一个 convert 转换消息的对象;        2.添加 fastjson 的配置信息,比如是否要格式化返回的 JSON 数据        3.在 convert 中添加配置信息        4.将 convert 添加到 converters 中         */        //1.定义一个 convert 转换消息对象        FastJsonHttpMessageConverter fastConverter=new FastJsonHttpMessageConverter();        //2.添加 fastjson 的配置信息,比如是否要格式化返回 JSON 数据        FastJsonConfig fastJsonConfig=new FastJsonConfig();        fastJsonConfig.setSerializerFeatures(                SerializerFeature.PrettyFormat        );        fastConverter.setFastJsonConfig(fastJsonConfig);        converters.add(fastConverter);    }

    单元测试

    Spring Boot 的单元测试很简单,直接看代码:

    @SpringBootTest(classes = DemoApplication.class)@RunWith(SpringJUnit4ClassRunner.class)public class TestDB {    @Test    public void test(){    }}

    模板引擎

    在传统的 SpringMVC 架构中,我们一般将 JSP、HTML 页面放到 webapps 目录下面,但是 Spring Boot 没有 webapps,更没有 web.xml,如果我们要写界面的话,该如何做呢?

    Spring Boot 官方提供了几种模板引擎:FreeMarker、Velocity、Thymeleaf、Groovy、mustache、JSP。

    这里以 FreeMarker 为例讲解 Spring Boot 的使用。

    首先引入 FreeMarker 依赖:

        <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-freemarker</artifactId>    </dependency>

    在 resources 下面建立两个目录:static 和 templates,如图所示:

    这里写图片描述

    其中 static 目录用于存放静态资源,譬如 CSS、JS、HTML 等,templates 目录存放模板引擎文件,我们可以在 templates 下面创建一个文件:index.ftl(freemarker 默认后缀为 .ftl),并添加内容:

    <!DOCTYPE html><html>    <head>    </head>    <body>        <h1>Hello World!</h1>    </body></html>

    然后创建 PageController 并添加内容:

    @Controllerpublic class PageController {    @RequestMapping("index.html")    public String index(){        return "index";    }}

    启动 Application.java,访问:http://localhost:8081/api/index.html,就可以看到如图所示:

    enter image description here

    第03课:Spring Boot 启动原理第04课:初识 Spring Cloud
    第05课:服务注册与发现
    第06课:服务网关
    第07课:服务消费者
    第08课:服务异常处理
    第09课:配置中心
    第10课:消息总线
    第11课:服务链路追踪
    第12课:分布式锁
    第13课:分布式事务
    第14课:Spring Cloud 实例详解——基础框架搭建(一)
    第15课:Spring Cloud 实例详解——基础框架搭建(二)
    第16课:Spring Cloud 实例详解——基础框架搭建(三)
    第17课:Spring Cloud 实例详解——业务代码实现
    第18课:Spring Cloud 实例详解——系统发布
    第19课:Spring Cloud 源码解析
    第20课:K8S+Docker 部署 Spring Cloud 集群

    点击购买,可阅读课程全文

    展开全文
  • SSD原理解读-从入门到精通

    万次阅读 多人点赞 2018-08-27 08:00:47
    SSD原理解读-从入门到精通

    前言

    当初写这篇博客的初衷只是记录自己学习SSD的一些心得体会,纯属学习笔记,后来由于工作上的需要,需要对小伙伴进行目标检测方面的培训,后来就基于这篇博客进行了扩展,逐渐演变成了现在的样子,本文力求从一个初学者的角度去讲述目标检测和SSD(但是需要你具备CNN的基础),尽量使用通俗的语言并结合图表的方式让更多初学者更容易理解SSD这个算法,但是一个人的时间精力有限,不可能深入理解SSD的每一个细节,加上表达能力也有限,自己理解了的东西不一定在文中能够说明白,文中有什么不妥的地方,欢迎大家批评指正,也欢迎留言一起交流讨论。

    PS:
    工作中会经常遇到小伙伴问关于SSD和YOLO推理实现的问题,可以参考这篇博客:OpenCV加载onnx模型实现SSD,YOLOV3,YOLOV5的推理



    目标检测基础

    在实际的工作中发现,一些小伙伴在学习检测的时候对一些基础的东西理解的还不够,这里总结了比较重要的几点。

    传统目标检测的基本原理

    为什么要提传统目标检测呢?因为理解传统目标检测对于理解基于深度学习的目标检测非常重要,因为学到最后你会发现,两者的本质都是一样的,都是对滑动窗口的分类。下面我们看一下传统目标检测的基本原理。
    在这里插入图片描述
    主要分为两个步骤:训练+预测,其中训练主要是用来得到分类器,比如SVM,预测就是使用训练好的分类器对图像中的滑动窗口进行特征提取然后分类,最后得到检测的结果。下面以人脸检测为例:
    在这里插入图片描述
    假设我们需要训练一个人脸检测器,那第一步就是训练一个人脸的分类器,这个分类器有什么作用呢?它的作用就是将上图左边的很多图像划分为两类:人脸和非人脸。分类器训练好了之后,就可以进行检测了。
    在这里插入图片描述
    预测阶段有两种滑动窗口策略:

    1. 策略1:使用不同大小的滑动窗口,对每个滑动窗口提取特征并分类判断是否是人脸,最后经过NMS得到最后的检测结果,本文的SSD本质上就是这种策略,不同检测层的anchor就类似于不同大小的滑动窗口
    2. 策略2:构造图像金字塔,只使用一种大小的滑动窗口在所有金字塔图像上滑动,对每个滑动窗口提取特征并分类判断是否是人脸,最后经过NMS得到最后的检测结果,MTCNN就是采用了这种策略

    传统目标检测的代表主要有:

    1. HOG+SVM的行人检测
    2. Haar+Adaboost的人脸检测

    为什么要使用全卷积神经网络做检测

    我们知道经典的深度学习目标检测算法的代表SSD,YOLOV3和FasterRCNN的RPN都使用了全卷积神经网络,那为什么要使用全卷积呢?

    分类网络

    下面是一个典型的分类网络结构:
    在这里插入图片描述
    图片来自经典的人脸识别论文DeepID2:https://arxiv.org/abs/1406.4773

    我们知道典型的分类网络,比如VGG,ResNet等最后都会使用全连接层提取特征,然后经过softmax计算每一类的概率。

    典型的分类网络有如下特点:

    1. Softmax的前一层为全连接层,且输出节点数为分类的类别数,比如imagenet有1000类,则最后的全连接层有1000个输出节点,分别对应1000类的概率,VGG16中的fc8对应了这一层,上图有10000分类,所以fc2有10000个输出节点
    2. Softmax的倒数第二层也为全连接层,这一层一般是提取特征用的,比如VGG16中的fc7层就是用来提取特征的,上图中DeepID2层也是用来进行提取特征的
    3. 由于使用全连接层提取特征,所以提取的是全图的特征,所以一张图像中只能包含一个目标,如果有多个目标,提取出来的特征就不准确,影响最后的预测
    4. 由于全连接层的存在,网络的输入大小必须是固定的

    那如何才能提取多个目标的特征呢?

    使用卷积层代替全连接层进行特征提取

    在这里插入图片描述

    我们将图1简化为图2的表示形式,其中图2中省略了Input到DeepID2中间的层,我们看到当DeepID2是全连接层的时候,感受野对应了全图,所以提取的是全图的特征,现在我们把DeepID2替换为卷积层,其中卷积层的输出通道数为160,这是为了能够提取160维的特征向量,图3中我们可以看到当使用卷积层的时候,DeepID2的输出特征图的每个位置的感受野不是对应了全图,而是对应了原图的某一个滑动窗口,这样DeepID2这一层就可以提取原图多个滑动窗口的特征了,图3中我们可以看到一共提取出了25个滑动窗口的160维特征向量,然后我们就可以对这25个滑动窗口做分类了。这样就使得检测成为了可能。

    使用卷积层代替全连接层进行分类

    我们知道在分类网络中softmax的前一层使用的是全连接层,且该全连接层的输出节点数为分类数,比如上文中的DeepID2的后面的fc2层。
    在这里插入图片描述
    但是现在DeepID2这一层变成了卷积层之后,fc2层就不适合采用全连接层了。下面我们将fc2层替换为卷积层,我们采用1x1卷积(也可以使用3x3卷积),同时卷积层的输出通道数为10000,对应了10000分类。
    在这里插入图片描述
    我们看到当fc2采用1x1卷积的时候,fc2层的特征图中的每个位置对应了一个滑动窗口的10000分类,这样一共得到25x10000的特征向量,表示对25个滑动窗口的10000分类。最后将这25x10000的特征向量输入softmax,就可以实现对这25个滑动窗口的分类了。

    这其实就是经典的OverFeat的核心思想。

    注意:这里并没有说检测网络不能使用全连接层,其实检测网络也可以使用全连接层。检测网络只是使用卷积层代替全连接层提取特征,最后对特征进行分类和回归可以使用卷积层也可以使用全连接层,YOLOV1最后就使用了全连接层对特征进行分类和回归,只是这样会有一些缺点:网络的输入大小必须是固定的,而且最后检测的效果往往没有使用卷积层做分类和回归的效果好。

    确定每个滑动窗口的类别

    上文中,我们将传统的分类网络一步一步的修改成了一个检测网络,知道了如何提取多个目标的特征以及使用卷积层代替全连接层进行分类,现在我们还差最后一步:要对多个目标进行分类,还需要知道他们的groundtruth,也就是他们的类别,那么如何确定类别呢?
    这里就要用到anchor这项技术。

    anchor就是用来确定类别的。

    在这里插入图片描述
    我们知道anchor的参数是可以手动设置的,上图中
    anchor的大小被设置为 [x1,y1,x2,y2],这个anchor就是上文中提到的滑动窗口
    groundtruth对应 [x1’,y1’,x2’,y2’]
    然后通过计算anchor和groundtruth之间的IOU就可以确定这个滑动窗口的类别了(比如IOU>0.5的为正样本,IOU<0.3的为负样本)。关于anchor在下文中会有详细讨论。

    到这里,SSD的整体框架已经基本搭建好了。其实SSD的多个检测层就等价于多个DeepID2层,不同检测层有不同大小的滑动窗口,能够检测到不同大小的目标。每个检测层后面会接2路3x3卷积用来做分类和回归,对应了fc2层。
    在这里插入图片描述
    理解了上面的内容之后,其实就很容易理解SSD了。


    SSD效果为什么这么好

    虽然SSD这个算法出来已经两年了,但是至今依旧是目标检测中应用最广泛的算法,虽然后面有很多基于SSD改进的算法,但依旧没有哪一种可以完全超越SSD。那么为什么SSD效果这么好?SSD效果好主要有三点原因:

    1. 多尺度
    2. 设置了多种宽高比的anchor
    3. 数据增强

    注:

    1. anchor,default box,prior box表示的是同一个意思,本文统一使用更加常用的anchor来表示。实际上,anchor技术的鼻祖是DeepMultiBox(2014年CVPR:Scalable Object Detection using Deep Neural Networks),这篇论文里首次提出使用prior(先验框),并且提出使用prior做匹配。后面的众多工作,比如RCNN系列,YOLO系列都使用了anchor这个术语,而在SSD中anchor又叫default box,本质上都是表示的同一个东西。

    原因1:多尺度

    这里写图片描述

    由SSD的网络结构可以看出,SSD使用6个不同特征图检测不同尺度的目标。低层预测小目标,高层预测大目标。

    通过前面的学习,其实就很容易理解SSD中的多尺度检测,这6个检测层都是卷积层,对应了上文中的6个DeepID2层,每个DeepID2层对应了不同大小的滑动窗口(低层滑动窗口较小,高层滑动窗口较大),这样就可以检测到不同尺度的目标了。
    这里写图片描述

    作者在论文中通过实验验证了,采用多个特征图做检测能够大大提高检测精度,从上面的表格可以看出,采用6个特征图检测的时候,mAP为74.3%,如果只采用conv7做检测,mAP只有62.4%。

    原因2:设置了多种宽高比的anchor

    这里写图片描述

    在特征图的每个像素点处,生成不同宽高比的anchor,论文中设置的宽高比为{1,2,3,1/2,1/3}。假设每个像素点有k个anchor,需要对每个anchor进行分类和回归,其中用于分类的卷积核个数为ck(c表示类别数),回归的卷积核个数为4k。

    SSD300中anchor的数量:(38384 + 19196 + 10106 + 556 + 334 + 114)= 8732

    对于初学者一定有以下几个问题:

    1. 为什么要设置anchor?
    2. 为什么同一个检测层可以设置不同大小的anchor?
    3. 为什么一个检测层可以设置多个anchor?
    4. 为什么要设置多种宽高比的anchor?

    理论感受野和有效感受野

    NIPS 2016论文Understanding the Effective Receptive Field in Deep Convolutional Neural Networks[1]提出了有效感受野(Effective Receptive Field, ERF)理论。有效感受野的理解对于理解anchor非常重要。

    影响某个神经元输出的输入区域就是理论感受野,也就是我们平时说的感受野,但该输入区域的每个像素点对输出的重要性不同,越靠近中心的像素点影响越大,呈高斯分布,也就是说只有中间的一小部分区域对最后的输出有重要的影响,这个中间的一小部分区域就是有效感受野

    有效感受野在训练过程中是会发生变化的,影响有效感受野的因素:

    1. 数据集
    2. 层的类型(下采样,扩张卷积,跳层连接,非线性激活函数)
    3. 卷积层参数初始化方式(Uniform(参数全部设置为1), Random)
    4. 卷积层的个数

    下图展示了不同因素对有效感受野的影响
    卷积层层数,权值初始化方式以及非线性激活对ERF的影响
    卷积层层数,权值初始化方式以及非线性激活对ERF的影响

    在这里插入图片描述
    下采样和扩张卷积可以增大感受野

    在这里插入图片描述
    不同数据集对感受野的影响

    为什么要设置anchor?

    通过前面的学习,我们知道,在分类/识别问题中,通常整张图就包含一个目标,所以只需要对一个目标直接分类就好了,所以最后直接使用全连接层提取整幅图像的特征就可以了(全连接层理论感受野大小就是输入大小)。但是检测问题中,输入图像会包含有很多个目标,所以需要对多个目标进行特征提取,这个时候就不能使用全连接层了,只能使用卷积层,卷积层的输出特征图上每个位置对应原图的一个理论感受野,该位置提取的就是这个理论感受野区域的特征,然后我们只需要对这个特征进行分类和回归就可以实现检测了。在实际训练训练过程中,要想对这个特征进行分类,我们就需要知道这个特征提取的是什么目标的特征,所以我们需要知道两个东西:

    1. 这个特征对应了原图什么区域?
    2. 这个区域的label是什么?
      但是在实际训练过程中,我们并不知道这个理论感受野的大小,这个时候就出现了anchor技术。

    anchor作用:通过anchor设置每一层实际响应的区域,使得某一层对特定大小的目标响应。这样检测层提取的就是anchor对应区域的特征了。通过anchor我们就可以知道上面两个问题的答案了。

    论文中也提到:
    Feature maps from different levels within a network are known to have different (empirical) receptive field sizes. Fortunately,
    within the SSD framework, the default boxes do not necessary need to correspond to the actual receptive fields of each layer. We design the tiling of default boxes so that specific feature maps learn to be responsive to particular scales of the objects.
    通过设计anchor的平铺可以使得特定的特征图对特定大小的目标进行响应。

    下图可以更加形象的表示anchor的作用
    在这里插入图片描述
    图a中黑色虚线区域对应图b中的理论感受野,红色实线框是手动设置的anchor大小
    图b是使用图a中的anchor参数训练出来的模型,其中整个黑色区域就是理论感受野(TRF),对应a图的黑色虚线区域,中间呈高斯分布的白色点云区域就是由于设置了anchor而实际发生响应的区域,这个区域就是有效感受野(ERF),我们用anchor等价表示这个区域。
    我们可以看到通过在图a中设置anchor可以设置该层实际响应的区域,使得对特定大小的目标响应。

    这里还有几个问题需要说明一下。

    1. 为什么anchor可以设置实际响应的区域?
      这就是CNN的神奇之处,这个问题目前我还不知道如何解释,就像目前的深度学习依然是不可解释的一样,这点就当作一个结论记住就可以了。

    2. 为什么同一个检测层可以设置不同大小的anchor
      我们知道可以通过anchor设置每一层实际响应的区域,使得某一层对特定大小的目标响应。
      是否每一层只能对理论感受野响应呢?有效感受野理论表明,每一层实际响应的区域其实是有效感受野区域,而且这个有效感受野区域在训练过程中会发生变化(比如不同数据集的影响等),正是由于有效感受野有这个特性,所以我们可以在同一个检测层设置不同大小的anchor,也就是说你既可以设置anchor大小为理论感受野大小,也可以将anchor大小设置为其他大小,最后训练出来的网络会根据你的设置对特定大小的区域响应。

    3. 为什么在同一个特征图上可以设置多个anchor检测到不同尺度的目标
      刚开始学SSD的朋友一定有这样的疑惑,同一层的感受野是一样的,为什么在同一层可以设置多个anchor,然后在分类和回归两个分支上只需要使用不同通道的3x3卷积核就可以实现对不同anchor的检测?虽然分类和回归使用的是同一个特征图,但是不同通道的3x3卷积核会学习到那块区域的不同的特征,所以不同通道对应的anchor可以检测到不同尺度的目标。

    4. anchor本身不参与网络的实际训练,anchor影响的是classification和regression分支如何进行encode box(训练阶段)和decode box(测试阶段)。测试的时候,anchor就像滑动窗口一样,在图像中滑动,对每个anchor做分类和回归得到最终的结果。

    5. 关于anchor的进一步探讨,见另一篇博客:深入理解anchor,欢迎大家畅所欲言

    anchor与滑动窗口

    上文中提到了两个概念:滑动窗口和anchor,每个检测层对应了不同大小的滑动窗口,也对应着不同大小的anchor,那这两个概念有什么区别呢?

    这里我们不严格区分这两个概念,我们可以认为这两个表示的是同一个东西。

    这里有一点要注意:由于anchor是可以手动设置的,所以某一个检测层的滑动窗口大小是会随着anchor的大小发生变化的,这就是anchor的神奇之处!

    anchor的匹配

    前面我们知道了SSD提取的是anchor对应区域的特征,实际训练的时候还需要知道每个anchor的分类和回归的label,如何确定呢?SSD通过anchor与groundtruth匹配来确定label。
    在训练阶段,SSD会先寻找与每个anchor的IOU最大的那个ground truth(大于IOU阈值0.5),这个过程叫做匹配。如果一个anchor找到了匹配的ground truth,则该anchor就是正样本,该anchor的类别就是该ground truth的类别,如果没有找到,该anchor就是负样本。图1(b)中8x8特征图中的两个蓝色的anchor匹配到了猫,该anchor的类别为猫,图1©中4x4特征图中的一个红色的anchor匹配到了狗,该anchor的类别为狗。图2显示了实际的匹配过程,两个红色的anchor分别匹配到了猫和狗,左上角的anchor没有匹配,即为负样本。

    这里写图片描述
    图1

    在这里插入图片描述
    图 2(图中红色框表示anchor,绿色框表示groundtruth)

    关于匹配更多的细节,参考Caffe源码multibox_loss_layer.cpp中的FindMatches()函数,前面的博客:SSD源码解读3-MultiBoxLossLayer中也讲到了该函数。

    为什么要设置多种宽高比的anchor?

    由于现实中的目标会有各种宽高比(比如行人),设置多个宽高比可以检测到不同宽高比的目标。
    这里写图片描述

    作者实验结果表明,增加宽高比为1/2,2,1/3,3的default box,mAP从71.6%提高到了74.3%。

    如何选择anchor的scale和aspect ratio?

    假设我们用m个feature maps做预测,那么对于每个featuer map而言其anchor的scale是按以下公式计算的。
    Sk=Smin+SmaxSminm1(k1) S_k=S_{min}+{{S_{max}-S_{min}} \over {m-1}}(k-1)
    这里SminS_{min}是0.2,表示最低层的scale是0.2,SmaxS_{max}是0.9,表示最高层的scale是0.9。宽高比αr=1,2,3,1/2,1/3{\alpha}_r={1,2,3,1/2,1/3},因此每个anchor的宽wkα=Skαrw^{\alpha}_{k}=S_k \sqrt{{\alpha}_r},高hkα=Sk/αrh^{\alpha}_{k}={S_k / \sqrt{{\alpha}_r}},当aspect ratio为1时,作者还增加一种scale的anchor:Sk=SkSk+1S^{'}_k= \sqrt{S_kS_k+1},因此,对于每个feature map cell而言,一共有6种anchor。

    示例:
    假设m=6,即使用6个特征图做预测, 则每一层的scale:0.2,0.34,0.48,0.62,0.76,0.9
    对于第一层,scale=0.2,对应的6个anchor为:

    宽高比
    1 0.200000 0.200000
    2 0.282843 0.141421
    3 0.346410 0.115470
    1/2 0.141421 0.282843
    1/3 0.115412 0.346583
    最后增加的default box 0.260768 0.260768

    注:表格中每个宽高比的anchor的实际宽和高需要乘以输入图像的大小,如SSD300,则需要使用上面的数值乘以300得到anchor实际大小。

    Caffe源码中anchor的宽高比以及scale的设置参考prior_box_layer.cpp,前面的博客:SSD源码解读2-PriorBoxLayer也对该层进行过解读。

    原因3:数据增强

    SSD中使用了两种数据增强的方式
    1. 放大操作: 随机crop,patch与任意一个目标的IOU为0.1,0.3,0.5,0.7,0.9,每个patch的大小为原图大小的[0.1,1],宽高比在1/2到2之间。能够生成更多的尺度较大的目标
    2. 缩小操作: 首先创建16倍原图大小的画布,然后将原图放置其中,然后随机crop,能够生成更多尺度较小的目标

    这里写图片描述

    这里写图片描述

    作者实验表明,增加了数据增强后,mAP从65.5提高到了74.3!

    数据增强是SSD中最大的trick,已经成为了后面众多检测算法的标配了。

    数据增强对应Caffe源码annotated_data_layer.cpp,前面的博客:SSD源码解读1-数据层AnnotatedDataLayer也对该层进行过解读。


    SSD的缺点及改进

    1. SSD主要缺点:SSD对小目标的检测效果一般,作者认为小目标在高层没有足够的信息。

    论文原文:
    This is not surprising because those small objects may not even have any information at the very top layers. Increasing the input size (e.g. from 300× 300 to 512× 512) can help improve detecting small objects, but there is still a lot of room to improve.

    对小目标检测的改进可以从下面几个方面考虑:
    1. 增大输入尺寸
    2. 使用更低的特征图做检测(比如S3FD中使用更低的conv3_3检测)
    3. FPN(已经是检测网络的标配了)

    2. 关于anchor的设置的优化

    An alternative way of improving SSD is to design a better tiling of default boxes so that its position and scale are better aligned with the receptive field of each position on a feature map. We leave this for future work. P12
    论文中提到的anchor设置没有对齐感受野,通常几个像素的中心位置偏移,对大目标来说IOU变化不会很大,但对小目标IOU变化剧烈,尤其感受野不够大的时候,anchor很可能偏移出感受野区域,影响性能。
    关于anchor的设计,作者还提到了
    In practice, one can also design a distribution of default boxes to best fit a specific dataset. How to design the optimal tiling is an open question as well
    论文提到根据特定数据集设计default box,在YOLOV2中使用聚类的方式初始化anchor,能够更好的匹配到ground truth,帮助网络更好的训练


    SSD中的Mining机制

    在视觉任务中经常遇到两个问题:
    1. 类别不均衡
    2. 简单样本和困难样本不均衡 (easy sample overwhelming)。easy sample如果太多,可能会将有效梯度稀释掉。

    为了解决上述问题,研究人员提出了一些解决方案:
    1. Online Hard Example Mining, OHEM(2016)。将所有sample根据当前loss排序,选出loss最大的N个,其余的抛弃。这个方法就只处理了easy sample的问题。
    2. Focal Loss(2017), 最近提出来的。不会像OHEM那样抛弃一部分样本,focal loss考虑了每个样本, 不同的是难易样本上的loss权重是根据样本难度计算出来的。

    SSD中采用了一种新的Mining机制,OHNM(Online Hard Negative Mining),在Focal Loss里代号为OHEM 1:3,是对OHEM的一种改进。OHNM在计算loss时, 使用所有的positive anchor, 使用OHEM选择3倍于positive anchor的negative anchor。同时考虑了类间平衡与easy sample。通过OHNM让训练更快收敛同时也更加稳定。

    注意,SSD中mining具体实现的时候,MultiBoxLoss层的 mining_type可以选择MAX_NEGATIVE或者HARD_EXAMPL

    1. MAX_NEGATIVE对应OHNM, 只计算分类loss,不计算定位loss,只针对负样本选择loss最大的3倍于正样本数量的负样本
    2. HARD_EXAMPL对应OHEM ,会同时计算分类和定位loss,选择出loss最大的前topN个样本
      具体实现参考MineHardExamples()函数。

    SSD与MTCNN

    这里为什么会提到MTCNN[2]呢?如果你了解过MTCNN这个算法,一定对PNet这个网络不陌生,仔细比较SSD与PNet,你就会发现SSD与PNet之间有着千丝万缕的联系。

    这里写图片描述
    其实我对SSD的理解就是源于MTCNN中的PNet,实际上SSD可以看成是由6个不同的PNet组合而成。

    这里用原论文中的SSD300的结构与MTCNN作比较
    这里写图片描述

    这里写图片描述

    SSD与MTCNN的不同

    1. 生成训练数据的方式不同
      MTCNN需要事先手动将所有的训练样本生成好,然后输入到网络中训练, 而SSD不需要,SSD在训练过程中自动生成所有训练样本。SSD中实际的训练样本就是所有anchor,每个anchor的label由anchor与ground truth匹配来确定。SSD实现了真正意义上端到端的训练。
    2. MTCNN和SSD采用了两种不同的多尺度检测策略
      MTCNN:首先构建图像金字塔,然后使用固定大小的滑动窗口在金字塔每一级滑动,对每个滑动窗口分类回归。
      SSD: 在原图中设置了不同大小的滑动窗口,对不同大小的滑动窗口进行分类和回归。
      不管是MTCNN,还是SSD,本质上是对所有滑动窗口的分类。这与传统的目标检测方法本质上是一样的。
    3. Mining的方式不同
      MTCNN需要手动做mining,而SSD采用了OHNM训练过程中自动完成负样本的mining,解决了简单样本的问题和类别不平衡问题。
    4. 其实MTCNN中也是有anchor的, MTCNN中的anchor就是PNet的滑动窗口
      MTCNN的训练样本就是滑动窗口图像,而生成训练样本的时候使用滑动窗口与groundtruth进行匹配,得到分类和回归的label,所以anchor就是PNet的滑动窗口。不过与SSD的区别在于这个匹配过程不是训练过程中自动完成的,而是事先手动完成。
      判断什么是anchor的方法:使用什么匹配groundtruth的,那个就是anchor

    SSD与YOLOV3

    YOLOV3和SSD在很多地方都非常相似,连YOLOV3的作者都说了,YOLOV3只是借鉴了其他人的一些思想(So here’s the deal with YOLOv3: We mostly took goodideas from other people.),看完YOLOV3,我甚至都觉得YOLOV3就是抄的SSD。但是不管怎么样,目前工业界用的最多的目标检测算法就是SSD和YOLO。YOLOV3和SSD都使用了anchor技术+多尺度检测+FPN。但是在诸多细节上稍有不同。

    SSD与YOLOV3的不同

    1. 训练数据格式不同:SSD的训练数据坐标顺序为label x1 y1 x2 y2,而YOLOV3为 label x y w h(x,y表示中心点)
    2. 网络结构方面,对坐标偏移和类别预测的结构不同:SSD在每个检测层使用了两个分支分别做分类和回归,但是YOLOV3在每个检测层只使用了一个分支,也就是说只用了一个tensor预测了3部分内容:坐标偏移+目标置信度+分类置信度(通道数为4+1+classNum)。
    3. 坐标偏移量的编码形式不同。
      SSD和FasterRCNN使用了相同的编码方式在这里插入图片描述
      而YOLOV3使用了如下的编码方式
      在这里插入图片描述
      SSD的坐标偏移很好理解,就是ground truth和anchor之间的偏移,但是YOLOV3中的偏移有点不同,YOLOV3的代码中,cx和cy表示的是检测层特征图每个像素点的坐标(实际计算的时候,做了归一化处理),其实也就是anchor的中心点,所以第1,2个公式表示的就是预测框和anchor中心点的偏移,第3,4个公式和SSD的是一样的
    4. 训练中anchor的匹配策略不同。SSD中,如果一个anchor匹配到groundtruth了,就是正样本,否则为负样本,一个groundtruth可以有多个anchor匹配到,但是YOLOV3中,一个groundtruth只能有一个anchor匹配到,这个anchor负责预测这个groundtruth,而且YOLOV3用了双阈值处理,YOLOV3会忽略掉一部分样本。但是我在阅读作者源码的时候发现,源码和论文有点出入,训练的时候,并不是只有最佳匹配的anchor参与训练,只要IOU大于阈值的都会参与训练,但是在YOLOV2中,只有最佳匹配的anchor会参与训练。
    5. SSD使用了单阈值匹配,YOLOV3使用了双阈值匹配。前面也提到了这一点,SSD在区分正负anchor的时候,只用了一个阈值,但是YOLOV3使用了两个阈值。
    6. loss的形式不同。
      如果还有不同点没有列出的,欢迎留言补充。

    SSD和YOLOV3在众多细节上有所不同,但是孰优孰劣,目前还没有定论。只要用的好,这两个算法的性能都是非常不错的,能够熟练掌握其中的一种就够用了。


    与传统目标检测的关系

    基于深度学习的检测算法,不管是SSD,YOLOV3,FasterRCNN还是MTCNN,本质上都是一样的,都是对滑动窗口的分类


    结束语

    博客最初写于2018-08-27, 2019-11-10和2020-6-9分别对原博客做了两次重大修改,随着对SSD一遍又一遍的学习和思考,每一次都能够发现以前有些地方理解还是有点问题的,现在越来越能体会到,为什么经典需要反复阅读反复思考,每次都会有新的理解,每次都能发现不一样的东西。

    最后希望这篇文章能够帮助到你。


    2018-8-27 08:08:36
    Last Updated: 2020-6-9 14:11:48


    参考文献

    [1] [2016 NIPS] Understanding the Effective Receptive Field in Deep Convolutional Neural Networks
    [2] [2016 ISPL] Joint Face Detection and Alignment using Multi-task Cascaded Convolutional Networks
    [3] [2014 NIPS] Deep Learning Face Representation by Joint Identification-Verification


    非常感谢您的阅读,如果您觉得这篇文章对您有帮助,欢迎扫码进行赞赏。
    这里写图片描述

    展开全文
  • 对于SpringCloud,很多小伙伴问了我的研究学习资料来源,除官方文档外,特例完整整理一下自己的平时参考学习其他资料,以及分享实战项目源码和代码资源,供大家参考学习 主要教程:SpringCloud教程 Spring ...

    对于SpringCloud,很多小伙伴问到了我的研究学习资料来源,除官方文档外,特例完整整理一下自己的平时参考学习其他资料,以及分享实战项目源码和代码资源,供大家参考学习

    主要教程:SpringCloud教程

    Spring Cloud Greenwich,基于Spring Boot 2.1.7,Github源码仓库案例下载

    一、我的教程(2020.03更新)

    SpringCloud从入门到精通教程(Greenwich版本)

    SpringCloud代码演示集合,持续更新中...

    1. SpringCloud从入门到精通教程(一)- 服务的注册与发现(Eureka)
    2. SpringCloud从入门到精通教程(二)- 服务提供者
    3. SpringCloud从入门到精通教程(三)- 服务消费者,实现方式一(ribbon)
    4. SpringCloud从入门到精通教程(四)- 服务消费者,实现方式二(feign)
    5. SpringCloud从入门到精通教程(五)- 高可用的服务注册中心(Eureka)
    6. SpringCloud从入门到精通教程(六)- 断路器/服务消费者(ribbon + hystrix)
    7. SpringCloud从入门到精通教程(七)- 断路器/服务消费者(feign + hystrix)
    8. SpringCloud从入门到精通教程(八)- 动态路由网关(zuul)
    9. SpringCloud从入门到精通教程(九)- 动态路由网关限流(zuul+ratelimit)
    10. SpringCloud从入门到精通教程(十)- 分布式配置中心
    11. SpringCloud从入门到精通教程(十一)- 高可用的分布式配置中心
    12. SpringCloud从入门到精通教程(十二)- 断路器监控(hystrix dashboard)
    13. SpringCloud从入门到精通教程(十三)- 路器聚合监控(hystrix turbine)
    14. SpringCloud从入门到精通教程(十四)- 服务链路追踪
    15. SpringCloud从入门到精通教程(十五)- 消息总线bus
    16. SpringCloud从入门到精通教程(十六)- 网关鉴权认证
    17. SpringCloud从入门到精通教程(十七)- SpringBoot Admin监控台
    18. SpringCloud从入门到精通教程(十八)- 项目实战/微服务架构设计

    同时,计划新增:

    SpringCloud Alibaba从入门到精通教程

    持续更新中...

    1. SpringCloud Alibaba从入门到精通教程(一)- 配置中心Nacos快速入门介绍·Server启动安装
    2. SpringCloud Alibaba从入门到精通教程(二)- 项目中快速集成配置中心·Nacos-服务注册发现功能 springcloud-alibaba-nacos-discovery
    3. SpringCloud Alibaba从入门到精通教程(三)- 项目中快速集成配置中心·Nacos-配置中心管理功能 springcloud-alibaba-nacos-config
    4. SpringCloud Alibaba从入门到精通教程(四)- 流控组件Sentinel快速入门介绍·Server启动安装
    5. SpringCloud Alibaba从入门到精通教程(五)- 项目中快速集成·限流组件Sentinel springcloud-alibaba-sentinel
    6. SpringCloud Alibaba从入门到精通教程(六)- 消息组件RocketMQ快速入门介绍·Server启动安装
    7. SpringCloud Alibaba从入门到精通教程(七)- 项目中快速集成·MQ消息组件RocketMQ springcloud-alibaba-rocketmq
    8. SpringCloud Alibaba从入门到精通教程(八)- 项目中快速集成·分布式事务组件Seata springcloud-alibaba-seata
    9. SpringCloud Alibaba从入门到精通教程(九)- 项目中快速集成·服务治理组件Dubbo springcloud-alibaba-dubbo
    10. SpringCloud Alibaba从入门到精通教程(十)- 项目中快速集成·限流组件Sentinel之限流数据源 springcloud-alibaba-sentinel-datasource
    11. SpringCloud Alibaba从入门到精通教程(十一)- 项目中快速集成·限流组件Sentinel之限流网关 springcloud-alibaba-sentinel-gateway
    12. SpringCloud Alibaba从入门到精通教程(十二)- 项目中快速集成·限流组件Sentinel之断路器 springcloud-alibaba-circuitbreaker-sentinel

    二、其他资料教程

    Finchley版本

    D版本

    三、杂篇

    进阶篇

    源码篇

    番外篇

    推荐GitHub学习资源

    附加

    我的专栏

     

     

    ------------------------------------------------------

    ------------------------------------------------------

     

    我的CSDN主页

    关于我(个人域名,更多我的信息)

    我的开源项目集Github

     

    期望和大家 一起学习,一起成长,共勉,O(∩_∩)O谢谢

    如果你有任何建议,或想学习的知识,可与我一起讨论交流

    欢迎交流问题,可加个人QQ 469580884,

    或者,加我的群号 751925591,一起探讨交流问题

    不讲虚的,只做实干家

    Talk is cheap,show me the code

     

    展开全文
  • SpringBoot从入门到精通教程

    万次阅读 多人点赞 2018-08-25 04:04:35
    对应的Github源码地址 SpringBoot从入门到精通教程(五)- 内嵌Tomcat自定义配置用法,对应的Github源码地址 SpringBoot从入门到精通教程(六)- Mysql和Mybatis+XML用法详解,对应的Github源码地址 SpringBoot...
  • Jmeter性能测试从入门到精通-全程实战 全程实战,每个知识点通过实际项目演练讲解 理论实践结合,既会做,又知道为什么这样做 讲解时同其他工具做对比,加深理解,了解区别 分享技巧,用起来事半功倍 从基础讲起,...
  • 《MyBatis 从入门到精通

    万次阅读 多人点赞 2017-06-21 20:46:33
    Git 入门到GitHub 入门,读者可以学会使用最流行的分布式版本控制系统和源代码托管服务。通过一段代码让大家了解 MyBatis 中的一部分关键类,通过代码包讲解可以了解MyBatis 每个包中所含的功能。最后通过MyBatis...
  • 从入门到精通,Java学习路线导航(附学习资源)

    万次阅读 多人点赞 2019-09-16 17:34:06
    最近也有很多人来向我"请教",他们大都是一些刚入门的新手,还不了解这个行业,也不知道何学起,开始的时候非常迷茫,实在是每天回复很多人也很麻烦,所以在这里统一作个回复吧。 Java学习路线 当然,这里我只是说...
  • Shader开发从入门到精通

    万人学习 2015-09-17 09:32:32
    Shader编程从入门到精通视频教程,该课程主要分享2D中的Shader与3D中的Shader编程,具体功能包括颜色配置、纹理、UV动画、滤镜等。
  • HTML 5移动开发从入门到精通

    万人学习 2015-01-14 20:16:03
    本课程讲述了HTML 5移动开发的各种技术,通过本课程的学习,用户可以掌握HTML 5移动开发的技巧
  • Flask从入门到精通

    万人学习 2019-12-11 10:39:34
    Flask 从入门到精通,课程内容包括:创建Flask项目、Flask路由、Flask Cookie、Flask Session、Flask-SQLAlchemy 数据库、Flask-WTF表单、Flask项目。
  • C语言编程从入门到精通

    千次下载 热门讨论 2011-11-22 21:44:53
    C语言编程从入门到精通 丰富的代码资源 详细的注释解释 全面的知识讲解 想学好C不得不看的经典源码
  • Android从入门到精通+源代码

    千次下载 热门讨论 2013-09-11 17:30:16
    Android从入门到精通+源代码 免费下载
  • HTML 5 从入门到精通

    千次下载 热门讨论 2013-08-18 23:21:36
    HTML5从入门到精通,HTML5教程pdf。教程通俗易懂,是新手学习html5的必备手册之一。
  • 黑苹果从入门到精通:最详细的VMware安装macOS教程

    万次阅读 多人点赞 2019-04-06 16:11:12
    系列的名字起的有点俗,叫做xxx从入门到精通,但是我很喜欢,相信过半的黑果群众都是程序员,作为教程来说这个名字俗但是好用,我也希望这个系列能像其它入门到精通系列一样,让大家学到东西...
  • 给大家带来的一篇关于Python入门相关的电子书资源,介绍了...内容介绍《Python从入门到精通》从新手视角去往,根据浅显易懂的語言、多种多样的案例,详解了应用Python开展软件开发应当把握的各层面技术性。全书共有...
  • 「VS Code」Visual Studio Code 菜鸟教程:从入门到精通

    万次阅读 多人点赞 2019-03-25 20:42:37
    Visual Studio Code,或简称为 VSCode,是我最喜欢的代码编辑器,我希望能有更多人享受 VSCode 的便捷与强大。然而已存教程不足以成为我们了解 VSCode 的窗口,很多具有广泛使用场景的功能,比如命令窗口和终端,...
  • python从入门到精通

    万人学习 2017-05-26 10:21:09
    您观看课程学习后 免费入群领取【超全Python资料包+17本学习电子书】 Python入门到精通视频教程,秉承打造初级到Python运维开发的实战课程,采用诸多一线案例如人人车,新浪等真实企业项目。
  • unity4.x从入门到精通,完整扫描版,全彩

    千次下载 热门讨论 2014-02-10 19:57:52
    Unity 首本官方教材《Unity 4.x从入门到精通》 PDF电子书。 Unity 首本大中华区官方教材上架销售。 Unity 大中华地区最权威、最专业、最完整的官方教材。全本精装彩印,Unity 官方主编,各大书店同时发售! 本书由...
  • Visual C# 2012从入门到精通

    千次下载 热门讨论 2013-12-11 11:06:02
    Visual C# 2012从入门到精通电子书
  • 黑马程序员 linux从入门到精通配套笔记 黑马程序员 linux从入门到精通配套笔记
  • Kali Linux 从入门到精通(一)-概论

    万次阅读 多人点赞 2019-01-02 20:27:56
    Kali Linux 从入门到精通(一)-概论 基本介绍 1.安全目标 先于攻击者发现和防止漏洞出现 攻击型安全 防护型安全 2.渗透测试 尝试挫败安全防御机制,发现系统安全弱点 从攻击者的角度...
  • PHP从入门到精通完整笔记

    热门讨论 2015-06-02 19:13:31
    PHP从入门到精通完整笔记,两个文档适合入门的看看
  • java从入门到精通pdf

    千次下载 热门讨论 2011-02-09 01:02:09
    java从入门到精通,初学者的不二选择,让你一个月,从菜鸟成长为专家
  • HTML+CSS网页设计与布局从入门到精通

    千次下载 热门讨论 2013-03-18 19:28:53
    《HTML+CSS网页设计与布局从入门到精通》紧密围绕网页设计师在制作网页过程中的实际需要和应该掌握的技术,全面介绍了使用HTML和CSS进行网页设计和制作的各方面内容和技巧。 《HTML+CSS网页设计与布局从入门到精通》...
  • Ubuntu_Linux从入门到精通

    热门讨论 2010-09-17 11:32:11
    Ubuntu_Linux从入门到精通Ubuntu_Linux从入门到精通Ubuntu_Linux从入门到精通Ubuntu_Linux从入门到精通Ubuntu_Linux从入门到精通Ubuntu_Linux从入门到精通Ubuntu_Linux从入门到精通Ubuntu_Linux从入门到精通Ubuntu_...
  • java从入门到精通光盘代码

    千次下载 热门讨论 2015-11-25 00:41:13
    JAVA从入门到精通随书下载的的光盘代码
  • CUDA从入门到精通

    万次阅读 多人点赞 2013-10-17 11:22:30
    CUDA从入门到精通(零):写在前面 在老板的要求下,本博主从2012年上高性能计算课程开始接触CUDA编程,随后将该技术应用到了实际项目中,使处理程序加速超过1K,可见基于图形显示器的并行计算对于追求速度的应用来...
  • Matlab经典教程——从入门到精通.pdf

    千次下载 热门讨论 2009-10-09 18:17:37
    Matlab经典教程——从入门到精通.pdf Matlab经典教程——从入门到精通.pdf Matlab经典教程——从入门到精通.pdf Matlab经典教程——从入门到精通.pdf Matlab经典教程——从入门到精通.pdf Matlab经典教程——从入门...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 46,561
精华内容 18,624
关键字:

从入门到精通