目录

    • 前言
      • 为何我决定写Spring Cloud专栏?
    • 正文
      • 版本约定
      • 关于Spring Cloud Context
        • 本jar的spring.factories文件解释
        • Spring Cloud专属配置类
      • Spring Boot容器 vs Spring Cloud容器
    • 总结
  • 关注A哥

 

前言

各位小伙伴大家好,我是A哥。了解我文章的小伙伴知道,到目前为止我还几乎从没写过有关Spring Boot/Spring Cloud的文章,这是为何?作为时下Java社区最火的技术(甚至没有之一),一般都恨不得往上靠,我这有点反其道而行之的赶脚。虽然自己已经写了不少关于Spring Framework的内容,但仍旧会被不少小伙伴认为不免有点与时代脱节呀。

都什么年代了,谁还会直接使用Spring Framework呢?是的没毛病,那么至于为何我一直坚持Spring Framework才是最重要的呢,请先看下图哈:

一、为何我决定写Spring Cloud专栏_Spring Boot
图很简单,但我想强调的道理是:你对Spring Framework的了解程度决定了你对Spring Boot的了解程度,而你对Spring Boot的了解程度又决定了你对Spring Cloud的了解程度。


为何我决定写Spring Cloud专栏?

一直迟迟不愿去写Spring Cloud系列,归根结底就一个字:有点害怕。万物都有正反面,凡事都是双刃剑,享受多少点赞,就得抗住多少被黑。其实,关于Spring Boot/Spring Cloud,主要是“市面上”的相关文章实在太多太多了,我感觉这汤饭被炒得太多次,有优有劣,被玩坏了的赶脚,所以在这part上,我还是心存畏惧的。

经过一段时间的思考,最终A哥还是决定要去“冒险”一波,毕竟那种自己被需要的感觉是极好的????。为何我还要趟这趟浑水呢?主要有如下两方面原因促成:

  1. 根据收集到的反馈,对Spring Cloud成体系的学习(非碎片化)还是有不小诉求的。这很容易理解:谁让它这么火呢~
  2. “市面上”的SB/SC相关文章/教程,大都偏使用层面。为了追求普适性,站在使用者的角度去书写和分析。读完了可能会Run了,可成为一个合格的API调用工程师,但背后的缘由、对于如何扩展仍举步维艰

我知道,教你如何使用的文章一般是最受欢迎的,比如每个人都知道有一种畅销书叫:从入门到精通、从入门到放弃、从入门到…但作为一向目的只需聚集小众的我,也是希望本专栏能写得更深入些,让内容和而不同

还记得我刚入行时我“师父”对我说过一段话,结合现在语境翻译为:如果你对SB/SC不甚了解,并且只是抱着一种just run的态度去对待,那你的工作会很累且难有积累和提升。不知道为何能run和为何不能run,做起事来心里必然会有所顾虑。Spring Boot/Spring Cloud对初学者非常的友好 ,并且兼具了极大的灵活性,因此不管是对于新手还是老手它都具有很大的学习以及可扩展、可定制空间。

熟悉A哥的小伙伴都知道,我虽然骚气,但是我写的技术文还是比较乏味,因为我一直觉得从玩中去学就是个伪命题,不存在的。要想学好,就必须耐得住寂寞的禁锢。特别是深入研究“内裤里面”的内容时,如果你脑子里尽是那些Picture,那你能学到啥?

随着国内IT行业的发展,从业人员也越来越多,所以技术圈也慢慢的更加浮躁了。相比之下,我反而更愿意沉下心去写些更为实用的文章,来追求那小众的共鸣声。这不,知识星球(见文末)是我建的理想小众圈子,人不多,但都很重要。我解释下为何搞技术文不讨喜,是一种苦行僧的修行之路:

  • 技术太过于枯燥,较少愿意静心阅读,这是人性使然。就比如你在地铁上,你是刷抖音开森,还是看代码愉快?
  • 技术文一般很难单独成篇,它对上下文(特别是上文)是有依赖的,这也是有时候看不下去的重要原因之一

看着我那些记述文的浏览量我就心酸????,这么做有点费力不讨好之嫌。利益驱使,因此坚持这么做的是比较少的,说实话,我也不知自己还能坚持多久呢。但80岁之前应该都是可以坚持到的吧,毕竟论坚持,A哥每次都是半小时起步(sorry,一不小心开车了,速度不快,求别投诉~)。

一、为何我决定写Spring Cloud专栏_Spring Boot_02
在现在大环境对对平民技术人不怎么乐观的当下,唯有技术过硬是你的立身之本,大潮退去才知道谁在裸泳,这对真正的技术人或许是机遇勒。那么来吧,跟着A哥一起的修行之路从这启程,期待本系列做得有血、有肉、有内容、有深度…

一、为何我决定写Spring Cloud专栏_SC_03


正文

何为Spring Cloud?摘抄自官网:Spring Cloud为开发人员提供了工具,以快速构建分布式系统中的一些常见模式(例如,配置管理,服务发现,断路器,智能路由,微代理,控制总线)。分布式系统的协调导致样板式样,并且使用Spring Cloud开发人员可以快速站起来实现这些样板的服务和应用程序。它们可以在任何分布式环境中正常工作,包括开发人员自己的笔记本电脑,裸机数据中心以及Cloud Foundry等托管平台。

A哥看到很多SC系列开门见山是教你如何去搭建应用,把SC跑起来。但作为只写“高逼格”文章的我自然不会从那聊起喽,也费你时间不是。本系列会认为你已经对SB有一定的了解,并且基本能够正常使用SC了,这样效果最佳。

关于Spring Boot我并无打算写它,但我写了一篇“总结篇”,请移步此处参阅:不懂SpringApplication生命周期事件体系?那就等于不会Spring Boot嘛

本文单刀直入,上来就是源码分析以及对应实例讲解,嘎嘎就是干,一把嗦。所以我们学习Spring Cloud那必然先来到它的核心中的核心:Spring Cloud Context


版本约定

不约定版本的专题讲解是不负责任的,特别对于这么“浪”的SC来说尤甚(毕竟不向下兼容在它身上太常见了)。版本上为了和A哥的其它专题系列保持一致方便后续串讲,本文对相关库的版本做出如下约定:

  • Spring Boot版本:2.2.2.RELEASE
  • Spring Cloud版本:Hoxton.SR1
    • Spring Boot版本:2.2.2.RELEASE
    • spring-cloud-starter-xxx版本:2.2.1.RELEASE

另外由于spring-boot-starter-webspring-boot-starter-actuator这两个启动器几乎是所有工程必导的,因此本利我也一并导入,也方便了后面做Demo测试~

说明:你阅读到本文时Spring Cloud版本可能已经到了Hoxton.SR4,Spring Boot版本也可能已经到了2.2.6.RELEASE。但是像这种专栏式的学习,你完全可忽略小版本之间的差异,姑且认为他们是一样的即可

工程pom文件的大致内容如下:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.2.RELEASE</version>
    <relativePath/>
</parent>

...

<properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>Hoxton.SR1</spring-cloud.version>
</properties>

...

<!-- 几乎所有SB工程必导 -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    ...
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

关于Spring Cloud Context

也许你从没听过SC的Context上下文概念,或者从没听过spring-cloud-context这个工程,这都是非常正常的,毕竟实际Just Run条件下确实无需知晓,对初学者非常友好嘛,这是必须做到的。但你应该在pom里见过or导入过spring-cloud-starter这个jar:

一、为何我决定写Spring Cloud专栏_Spring Cloud_04

  • spring-cloud-context:SC的context上下文、配置、生命周期管理、健康检查等等
  • spring-cloud-commons:大名鼎鼎的SC的commons抽象。含有如:服务注册/发现、熔断器、负载均衡等公共抽象,面向该抽象编程可以无需关心底层实现,带来一致的编程感受,这是Spring家族最擅长干的事

很明显,本系列将把它拆散开来逐个解释其作用,目的是深入理解,从本质上感受SC的设计以及它的抽象方式和实现方式。那么首先就来到spring-cloud-context这个工程喽。

GAV如下:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-context</artifactId>
</dependency>

一、为何我决定写Spring Cloud专栏_SB_05

说明:实际生产中,此jar你一般不会单独导入,而是伴随着Spring Cloud starter一起带入的。但“讲课”就得基于最小化原则进行讲解嘛,因此我需要最小化的依赖,我的工程里只导入此jar


本jar的spring.factories文件解释

这个jar很“单纯”,并不拖泥带水,它里面存在一个spring.factories文件,内容如下:

到这里了可别问spring.factories是什么?有什么作用之类的话了哈。它是Spring SPI机制(并非Spring Boot提供的功能哟)的实现,具体参考这个类SpringFactoriesLoader

# AutoConfiguration 自动配置类(一般情况下只有SB容器去执行它,但是不是确定的)
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.cloud.autoconfigure.LifecycleMvcEndpointAutoConfiguration,\
org.springframework.cloud.autoconfigure.RefreshAutoConfiguration,\
org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration,\
org.springframework.cloud.autoconfigure.WritableEnvironmentEndpointAutoConfiguration


# Application Listeners 监听器们
org.springframework.context.ApplicationListener=\
org.springframework.cloud.bootstrap.BootstrapApplicationListener,\  -> 监听ApplicationEnvironmentPreparedEvent事件
org.springframework.cloud.bootstrap.LoggingSystemShutdownListener,\ -> 监听ApplicationEnvironmentPreparedEvent事件
org.springframework.cloud.context.restart.RestartListener -> 监听ApplicationPreparedEvent/ContextRefreshedEvent事件


# Bootstrap components 由Spring Cloud容器负责加载的配置类们
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration

此配置文件内容一共分为三个部分,会涉及到执行顺序的问题:

再次有必要强调一次,建议你已经掌握了此文的知识效果更佳:不懂SpringApplication生命周期事件体系?那就等于不会Spring Boot嘛

  1. 自动配置类。有的小伙伴认为自动配置类只能是Spring Boot容器才去执行它,其实这是不对的。确切的说:只要容器内开启了自动配置(有Bean标注了@EnableAutoConfiguration注解,实际是向容器导入了AutoConfigurationImportSelector)就都会执行执行spring.factories里的自动配置类
    1. Spring Boot自然不用说,@SpringBootApplication里就默认开启了自动配置
    2. Spring Cloud里,虽然也使用SpringApplication去启动容器,但是因为SC容器并没有开启自动配置,所以它不会执行
    3. 强调说明:自动配置的执行时机是refresh()容器启动时(执行AutoConfigurationImportSelector#selectImports)。另外,如果你的容器并不需要开启自动配置(就像SC一样),请不要开启,挺耗时的(前提是你对SB、SC有足够了解,不然容易出bug)。或者你也可以使用ImportAutoConfigurationImportSelector来手动接管
  2. 监听器。如上标注,这几个监听器都是监听的SpringApplication的生命周期事件,因此它们在SB的容器启动时的不同生命周期里会按顺序执行,特别是最重要的BootstrapApplicationListener它负责Spring Cloud容器的启动工作
    1. 监听器是由SpringApplication应用去加载的,而SC也是由SpringApplication去启动的,所以SC也会让这些监听器挨个执行哦,若有必要,请注意“防范”
  3. Spring Cloud的启动/引导配置。这些配置是SC专属,详见下面的说明

Spring Cloud专属配置类

也叫Bootstrap引导容器的专属配置类。通过上面3步的第一步解释可知:Spring Cloud它并不会去执行/加载你配置的EnableAutoConfiguration自动配置类,那么如果SC它在引导期间需要属于自己的配置怎么办呢???

这就到了SC它专为自己开发出来的一套配置机制,姑且把它叫做Spring Cloud专属配置类。不同于Spring Boot自动配置使用的是org.springframework.boot.autoconfigure.EnableAutoConfiguration,它作为SC专属的,自然key也就不能一样喽,使用的是:org.springframework.cloud.bootstrap.BootstrapConfiguration@BootstrapConfiguration注解的全类名)。通过这个key配置的配置类们(可以是普通配置类,也可以是自动配置类),就是只能被SC容器在启动时加载的专属配置类,是SC父容器专属。

SB的自动配置类使用AutoConfigurationImportSelector负载最后加载,此处SC的专属配置类使用的是BootstrapImportSelector负责加载,它俩的共同点是:都是一个DeferredImportSelector,所以执行时机是最晚的

通过以上的了解,我们可以知道,SC的专属配置类有如下几个特点:

  1. 该配置只能被Spring Cloud容器/Bootstrap引导上下文读取/加载
  2. 该配置的加载时机最早:优先于SB的普通配置、自动配置之前加载,所以它是具有最高优先级的配置类
  3. 该配置存在于父容器(SC容器)内,而非SB容器内
    1. 当然喽,SB容器也可以访问

Spring Boot容器 vs Spring Cloud容器

我们知道,Spring Cloud容器是Spring Boot容器的父容器。为了让你更直观的看到父子容器内的Bean情况(个数 + 详情),了解其区别,此处A哥写个最简案例比较一波:

public static void main(String[] args) {
    ConfigurableApplicationContext bootContext = SpringApplication.run(Application.class, args);
    System.out.println("boot容器类型" + bootContext.getClass());
    System.out.println("boot容器Bean定义总数:" + bootContext.getBeanFactory().getBeanDefinitionCount());
    System.out.println("boot容器Bean实例总数:" + bootContext.getBeanFactory().getSingletonCount());

    ConfigurableApplicationContext cloudContext = (ConfigurableApplicationContext) bootContext.getParent();
    System.out.println("cloud容器类型" + cloudContext.getClass());
    System.out.println("cloud容器Bean定义总数:" + cloudContext.getBeanFactory().getBeanDefinitionCount());
    System.out.println("cloud容器Bean实例总数:" + cloudContext.getBeanFactory().getSingletonCount());
}

控制台输出:

boot容器类型class org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
boot容器Bean定义总数:272
boot容器Bean实例总数:283

cloud容器类型class org.springframework.context.annotation.AnnotationConfigApplicationContext
cloud容器Bean定义总数:22
cloud容器Bean实例总数:34

SC容器(父容器)里的“内容”是远远少于SB容器(子容器的)。由于父容器里内容较少,可以详细看看。A哥这里帮你截图让你更直观的感受一把喽:

context.getBeanFactory().getBeanDefinitionNames()(共22个):

一、为何我决定写Spring Cloud专栏_Spring Cloud_06
context.getBeanFactory().getSingletonNames()(共34个):

一、为何我决定写Spring Cloud专栏_Spring Boot_07
看完了这个结果,相信你有个感官的认识了。那么A哥给小伙伴们提3个小问题哈,你可以自行思考:

  1. 为何一个是34,一个是22,这两个值为毛不等呢?差异在哪儿呢?
  2. SB容器为毛只是单单启动完,就有这么多Bean了呢?为何它如此重呢?
  3. SB应用有几个context上下文?SC应用呢?

总结

关于Spring Cloud系列的第一篇内容就写到这喽。本文先解释了为何我要写这个系列,以及简单的介绍了下Spring Cloud Context工程,通过本文你是能够大概清楚A哥后面将如何展开的,比如下文将是本系列的主菜之一:BootstrapApplicationListener监听器负责Spring Cloud容器的引导/启动/初始化,敬请关注。