本系列为之前系列的整理重启版,随着项目的发展以及项目中的使用,之前系列里面很多东西发生了变化,并且还有一些东西之前系列并没有提到,所以重启这个系列重新整理下,欢迎各位留言交流,谢谢!~

我们实现的 Spring Cloud 微服务框架,里面运用了许多 Spring Cloud 组件,并且对于某些组件进行了个性化改造。那么对于某个 Spring Cloud 组件,我们一般是如何入手理解其中的原理呢?以及如何知道其中的扩展点呢?一般从下面两个方面入手:

  1. 通过 spring-boot SPI 机制查看模块的扩展点
  2. 查看该模块实现的 NamedContextFactory

spring-core 项目中提供了 Spring 框架多种 SPI 机制,其中一种非常常用并灵活运用在了 Spring-boot 的机制就是基于 spring.factories 的 SPI 机制。

那么什么是 SPI(Service Provider)呢? 在系统设计中,为了模块间的协作,往往会设计统一的接口供模块之间的调用。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码,而是将指定哪个实现置于程序之外指定。Java 中默认的 SPI 机制就是通过 ServiceLoader 来实现,简单来说就是通过在META-INF/services目录下新建一个名称为接口全限定名的文件,内容为接口实现类的全限定名,之后程序通过代码:

//指定加载的接口类,以及用来加载类的类加载器,如果类加载器为 null 则用根类加载器加载
ServiceLoader<SpiService> serviceLoader = ServiceLoader.load(SpiService.class, someClassLoader);
Iterator<SpiService> iterator = serviceLoader.iterator();
while (iterator.hasNext()){
SpiService spiService = iterator.next();
}

获取指定的实现类。

在 Spring 框架中,这个类是SpringFactoriesLoader,需要在META-INF/spring.factories文件中指定接口以及对应的实现类,例如 Spring Cloud Commons 中的:

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.cloud.client.HostInfoEnvironmentPostProcessor

其中指定了EnvironmentPostProcessor的实现HostInfoEnvironmentPostProcessor

同时,Spring Boot 中会通过SpringFactoriesLoader.loadXXX类似的方法读取所有的EnvironmentPostProcessor的实现类并生成 Bean 到 ApplicationContext 中:

EnvironmentPostProcessorApplicationListener

//这个类也是通过spring.factories中指定ApplicationListener的实现而实现加载的,这里省略
public class EnvironmentPostProcessorApplicationListener implements SmartApplicationListener, Ordered {
//创建这个Bean的时候,会调用
public EnvironmentPostProcessorApplicationListener() {
this(EnvironmentPostProcessorsFactory
.fromSpringFactories(EnvironmentPostProcessorApplicationListener.class.getClassLoader()));
}
}

EnvironmentPostProcessorsFactory

static EnvironmentPostProcessorsFactory fromSpringFactories(ClassLoader classLoader) {
return new ReflectionEnvironmentPostProcessorsFactory(
//通过 SpringFactoriesLoader.loadFactoryNames 获取文件中指定的实现类并初始化
SpringFactoriesLoader.loadFactoryNames(EnvironmentPostProcessor.class, classLoader));
}

META-INF/spring.factories 文件中不一定指定的是接口以及对应的实现类,例如:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration,\
org.springframework.cloud.loadbalancer.config.BlockingLoadBalancerClientAutoConfiguration,\

其中EnableAutoConfiguration是一个注解,LoadBalancerAutoConfigurationBlockingLoadBalancerClientAutoConfiguration都是配置类并不是EnableAutoConfiguration的实现。那么这个是什么意思呢?EnableAutoConfiguration是一个注解,LoadBalancerAutoConfigurationBlockingLoadBalancerClientAutoConfiguration都是配置类。spring.factories这里是另一种特殊使用,记录要载入的 Bean 类。EnableAutoConfiguration在注解被使用的时候,这些 Bean 会被加载。这就是spring.factories的另外一种用法。

EnableAutoConfiguration是 Spring-boot 自动装载的核心注解。有了这个注解,Spring-boot 就可以自动加载各种@Configuration注解的类。那么这个机制是如何实现的呢?

来看下EnableAutoConfiguration的源码

EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
//排除的类
Class<?>[] exclude() default {};
//排除的Bean名称
String[] excludeName() default {};
}

我们看到了有 @Import 这个注解。这个注解是 Spring 框架的一个很常用的注解,是 Spring 基于 Java 注解配置的主要组成部分。

  1. 查看 jar 包的 META-INF/spring.factories
  2. 查看里面的内容,尤其关注 org.springframework.boot.autoconfigure.EnableAutoConfiguration= 自动加载的配置类
  3. 查看自动加载的配置类,关注哪些 Bean 可以扩展(例如,包含@ConditionalOnMissingBean 注解的 Bean)

我们一般想个性化定制都是针对调用不同微服务不同的 Bean 配置,所以其实要重点关注的就是这个模块扩展的 NamedContextFactory:

  1. 寻找这个组件扩展 NamedContextFactory 的类
  2. 查看类的源代码,查看默认配置是什么类,以及 Specification 是什么类,以及如何获取当前微服务的名称。
  3. 根据默认配置类,查看里面的 Bean 有哪些,并且哪些可以被替换(例如,包含@ConditionalOnMissingBean 注解的 Bean)
  4. 根据 Specification 查看扩展配置的方式

我们这里拿 spring-cloud-loadbalancer 举一个简单例子,即:

spring-cloud-loadbalancer 中扩展 NamedContextFactory 的类是 LoadBalancerClientFactory,查看 LoadBalancerClientFactory 的代码可以知道:

  1. 可以通过 loadbalancer.client.name 这个属性获取当前要创建的 Bean 是哪个微服务的
  2. 可以知道默认配置是 LoadBalancerClientConfiguration,再查看它里面的源代码我们可以知道主要初始化两个 Bean:
    1. ReactorLoadBalancer,负载均衡器,因为有 @ConditionalOnMissingBean 所以可以被替换,这就是我们的扩展点
    2. ServiceInstanceSupplier,提供实例信息的 Supplier,因为有 @ConditionalOnMissingBean 所以可以被替换,这就是我们的扩展点
  3. Specification 为 LoadBalancerSpecification,再分析其调用可以知道,可以通过 @LoadBalancerClient@LoadBalancerClientsLoadBalancerClientConfiguration 的基础上额外指定配置。

我们这一节详细分析了如何使用以及分析改造一个 Spring Cloud 组件。下一节我们将开始具体分析我们实现的微服务框架的每一块功能。

微信搜索“我的编程喵”关注公众号,每日一刷,轻松提升技术,斩获各种offer

SpringCloud升级之路2020.0.x版-9.如何理解并定制一个Spring Cloud组件的更多相关文章

  1. SpringCloud升级之路2020.0.x版-8.理解 NamedContextFactory

    本系列为之前系列的整理重启版,随着项目的发展以及项目中的使用,之前系列里面很多东西发生了变化,并且还有一些东西之前系列并没有提到,所以重启这个系列重新整理下,欢迎各位留言交流,谢谢!~ spring- ...

  2. SpringCloud升级之路2020.0.x版-1.背景

    本系列为之前系列的整理重启版,随着项目的发展以及项目中的使用,之前系列里面很多东西发生了变化,并且还有一些东西之前系列并没有提到,所以重启这个系列重新整理下,欢迎各位留言交流,谢谢!~ Spring ...

  3. SpringCloud升级之路2020.0.x版-41. SpringCloudGateway 基本流程讲解(1)

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 接下来,将进入我们升级之路的又一大模块,即网关模块.网关模块我们废弃了已经进入维护状态的 ...

  4. SpringCloud升级之路2020.0.x版-6.微服务特性相关的依赖说明

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford spring-cl ...

  5. SpringCloud升级之路2020.0.x版-10.使用Log4j2以及一些核心配置

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 我们使用 Log4 ...

  6. SpringCloud升级之路2020.0.x版-29.Spring Cloud OpenFeign 的解析(1)

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在使用云原生的很多微服务中,比较小规模的可能直接依靠云服务中的负载均衡器进行内部域名与服务 ...

  7. SpringCloud升级之路2020.0.x版-34.验证重试配置正确性(1)

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在前面一节,我们利用 resilience4j 粘合了 OpenFeign 实现了断路器. ...

  8. SpringCloud升级之路2020.0.x版-43.为何 SpringCloudGateway 中会有链路信息丢失

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在开始编写我们自己的日志 Filter 之前,还有一个问题我想在这里和大家分享,即在 Sp ...

  9. SpringCloud升级之路2020.0.x版-2.微服务框架需要考虑的问题

    本系列为之前系列的整理重启版,随着项目的发展以及项目中的使用,之前系列里面很多东西发生了变化,并且还有一些东西之前系列并没有提到,所以重启这个系列重新整理下,欢迎各位留言交流,谢谢!~ 上图中演示了一 ...

随机推荐

  1. Qt 串口通信之使用16进制发送数据的转换方式

    Qt 串口通信之使用16进制发送数据的转换方式 一 概述 有时候在做上位机串口通讯时,经常需要将字符串转成16进制的形式作为发送,借此分析记录一下. 二 需求分析 //假设需要转换的字符:如下 QSt ...

  2. 收集雪花(map)

    题目描述 不同的雪花往往有不同的形状.在北方的同学想将雪花收集起来,作为礼物送给在南方的同学们.一共有n个时刻,给出每个时刻下落雪花的形状,用不同的整数表示不同的形状.在收集的过程中,同学们不希望有重 ...

  3. 关于VIM的迁移

    将Gvim7.3从我笔记本拷到公司的电脑上面时, 这问题留了好久没有去解决.语法高亮无效不管我怎么设置 syntax enable,还是遇到这个问题. 后来在偶然的情况下,将我笔记本上面的文件在拷一份 ...

  4. 【源码篇】Flutter GetX深度剖析 | 我们终将走出自己的路(万字图文)

    前言 人心中的成见是一座大山,任你怎么努力都休想搬动. 这是电影<哪吒>里申公豹说的一句话,也是贯彻整部电影的一个主题:或许这句话引起了太多人的共鸣:35岁职场危机,大厂卡本科学历,无房无 ...

  5. RWLock——一种细粒度的Mutex互斥锁

    RWMutex -- 细粒度的读写锁 我们之前有讲过 Mutex 互斥锁.这是在任何时刻下只允许一个 goroutine 执行的串行化的锁.而现在这个 RWMutex 就是在 Mutex 的基础上进行 ...

  6. 访问其他人的vue项目

    本地git拉取项目 git clone git@git路径 项目clone到本地后 1.工具命令行切换到此项目路径下 cd 路径名称 2.首先要下载项目所需要的资源包  npm install 这里会 ...

  7. ctf之SusCTF2017-Caesar cipher

    由题目名字SusCTF2017-Caesar cipher可知,该题目考察凯撒密码. 直接下载附件打开如图 由题目描述可知,提交的flag格式为Susctf{}.在网上搜索在凯撒密码解密. 偏移量为3 ...

  8. MySQL字符串操作函数

    使用方法:concat(str1,str2,-)   返回结果为连接参数产生的字符串.如有任何一个参数为NULL ,则返回值为 NULL. mysql> select concat('11',' ...

  9. 学生信息管理系统--基于jsp技术和MySQL的简单增删改查

    web实现增删改查的方式有很多啊,对于初学者来说当然是要先了解各部分的传值的方式.本篇博客从jsp技术的最基础方面进行说明. 一.什么是jsp技术 首先,我们要了解什么是jsp技术. jsp技术是基于 ...

  10. 八大排序算法~简单选择排序【记录下标k变量的作用】

    八大排序算法~简单选择排序[记录下标k变量的作用] 1,思想:打擂台法,数组中的前n-1个元素依次上擂台"装嫩",后边的元素一个挨着一个不服,一个一个上去换掉它 2,优化:通过记录 ...