沧澜的博客

芝兰生于幽谷,不以无人而不芳


  • 首页

  • 归档

  • 分类

  • 标签

  • 搜索
软件思想 SpringBoot 领域驱动设计 算法 中间件 计算机网络 MySQL 数据库 javascript 极客时间 分布式架构 Jenkins JVM 多线程 Java基础 CentOS安装 编译OpenJDK 持续集成 杂谈

SpringBoot核心思想及源码解析(上)——自动装配

发表于 2021-07-18 | 分类于 Spring源码专题 | 0 | 阅读次数 204

引言

最近几年随着微服务的发展,SpringCloud火了,随之SpringBoot这个“脚手架”逐渐被大家所喜爱,主要是它的方便和快捷,无需各种xml配置,一个主方法直接跑一个后台程序,想整合其他框架时,只需要在pom文件中引入一个starter,类上加一个@EnableXXX的注解,就可以很快的集成其他框架。

image.png

那它是怎么实现的呢?对于一个神奇而又优秀的框架,我总喜欢一层一层的扒它的思想,逐渐解析其原理,今天我们主要先看一下自动装配的相关思想和实现。

自动装配思想浅析

从上图我们可以知道,SpringBoot其实就是帮我们导入了一些类,比如SpringMVC、Mybatis、Hibernate的一系列类定义,在启动的时候根据引入的starter来判断是否需要注入相关的Bean到容器

抛开SpringBoot已经实现的技术,已知Spring提供了很多后置处理器,根据这些已知的思想,假如SpringBoot如果是由我们来实现呢?

1. 已知需求:我们需要根据用户引入的jar包,批量帮其注册一批配置类?

(这里估计很多人都会说,SpringBoot程序不就是一个main函数,为什么要先从注解讲起来?其实从main函数讲起来也没毛病,但是那是服务启动的原理,放到源码解析(下)中讲,这里先跳过main函数解析的过程,假设main函数就是创建了一个Bean容器和内嵌的tomcat,随后我们直接跳到我们需要理解的自动装配;并且我们假设这里读者已经熟悉了Spring Bean加载的流程,如何解析Configuration的过程我们在Spring源码专题已经逐一解析)

回到Spring的解析配置中,后置处理器ConfigurationClassPostProcessor它主要在processConfigBeanDefinitions方法中从我们的根配置类(main函数的启动类)逐层扫描所有的配置类,它会按照以下的流程顺序逐渐解析注解中相关的Bean定义:

image.png

(注:图中为解析(装成BeanDefinition)顺序,非加载(解析BeanDefinition注册成Bean)顺序)

所以,我们根据上面的注解,可以考虑(传入配置类的方式优先淘汰,用户传入复杂度更高):

  1. @ComponentScan注解根据包扫描Bean定义,传入指定的包名,缺点无法掌握扫描顺序,且包名不能重复 (可以实现)
  2. @Import注解支持三种类型的Bean导入
    1. 普通的Configuration
    2. 支持批量导入的ImportSelector
    3. 支持动态注册Bean定义的ImportBeanDefinitionRegistrar
      可知Import注解的功能是非常强大的,我们优先考虑这种,缺点仍然是无法掌握加载顺序 (最佳实现)
  3. @Bean注解支持方法注册Bean定义,缺点硬编码无法动态配置 (最次实现)
  4. @ImportResource注解支持导入xml文件,缺点又回到到了Spring配置XML时代 (较次实现)

我们这里根据已知的注解实现,首先选择Import注解,导入我们需要的类,对于“需要”是根据我们已知的pom文件中的starter,其实就是根据一定的条件注入这些配置类,于是就有新的问题:如何根据条件导入配置类呢?

2. 已知需求:我们需要根据当前引入的Jar包,再判断是否引入相关配置类?

嗯,针对这个方向,我们首先想到的就是判断是否能够加载某个框架的核心类(此时可以联想到Spring4为什么要引入条件注解了吧),然后再根据条件导入某项特定的配置类,于是思路可以如下:

image.png

主要需要判断的条件如下,右边为对应Spring4引入的条件注解:

  1. 判断是否引入框架核心类:根据AppClassLoader判断是否引入相关框架的核心类(因为引入POM依赖后所有的Jar都会被AppClassLoader加载)————@ConditionalOnClass:类加载器已经加载过某类
  2. 判断是否存在用户的核心配置:根据BeanFactory判断是否已经扫描到用户配置的核心类(这一点依赖Bean的扫描顺序,直接判断有没有问题呢?)————@ConditionalOnBean、@ConditionalOnMissingBean:当容器中存在或者不存在时才实例化
  3. 判断是否处于Web容器:和1判断类似,使用AppClassLoader判断是否引入相关容器————@ConditionalOnWebApplication:项目是一个Web项目时进行实例化
  4. 用户自定义的条件...————@ConditionalOnExpression根据表达式计算为true时注入容器、@ConditionalOnProperty指定的属性有指定的值时进行实例化

对于筛选条件,我们已经解决了,那么关于上述第二点,@ConditionalOnBean和@ConditionalOnMissingBean依赖Bean加载的顺序,如果不实现顺序加载,就会出现以下问题:

image.png

之前我们了解到Spring对Bean的扫描是没有顺序可言的,顺序解释如下:

  1. Import导入相关核心包,需要判断@ConditionalOnMissingBean当没有配置某项Bean的时候使用自动装配的配置(比如@ConditionalOnMissingBean(CacheManager.class),判断用户未配置CacheManager时给定系统默认的CacheManager)
  2. 此时需要判断beanDefinition(Spring容器中的Bean定义是否存在),此时会根据扫描的顺序出现两种情况
    1. 之前未优先扫描用户包,判断配置类不存在装入容器,之后扫描到用户类出现重复定义异常(异常情况)
    2. 之前优先扫描过用户包,判断已经加载了指定的配置类,之后只会注入用户定义的配置类到容器(正常情况)

我们之前也说到,Spring没有定义顺序加载机制,如果默认情况则是按照文件系统的排序规则,Jar包的顺序;这些规则在不同操作系统上也不斤相同,所以是不可靠的,那么新的问题来了:

3. 已知需求:我们需要将判断逻辑的类延迟加载,用户定义的类全部在之前加载?

针对这一种场景,Spring4在ImportSelector新增了一个实现:DeferredImportSelector延迟加载的ImportSelector

  1. 延迟加载所有导入的实现类
  2. 自定义返回加载的顺序

image.png

这个流程在Spring源码解析中已经讲到过(可以翻阅以前文章进行复习),DeferredImportSelector主要实现的功能为让所有ImportSelector导入的类可以自定义加载顺序,并且这些类是在加载完成用户的@Bean、@Component、@Configuration注解之后的加载的

至此,我们自定义实现自动装配的逻辑已经完成了,实际上SpringBoot的核心思想就是如此,不过具体的实现逻辑要复杂很多,因为可能需要考虑各种场景,当然那是需要在实践细节中巩固了。


SpringBoot实现自动装配流程图

这里我们再看一下SpringBoot中实现的流程,可以先从总的流程来看,避免上来就过度陷入细节。SpringBoot的主程序从加上@SpringBootApplication注解,到将starter的类注入到容器,主要经历了以下结果流程。(边边角角的截图已经省略了,文末有processOn的链接可自取)

image.png

代码流程中,主要分为以下几步:

  1. @EnableAutoConfiguration注解 嵌套 @Import(AutoConfigurationImportSelector.class),而Import导入的这个类AutoConfigurationImportSelector实现了selectImports方法

  2. AutoConfigurationImportSelector类是自动装配的核心类,它还实现了DeferredImportSelector这个接口,简单来说它就是:

    1. 延后加载的ImportSelector
    2. 支持自定义的加载Bean顺序规则(这是一个比较重大的突破,在之前Bean的加载顺序完全取决于扫描包的顺序,也就是文件系统的本身顺序)
      (注:在此之前有@Order注解,它解决的问题是执行顺序,和加载顺序无关)

DeferredImportSelector 提供了接口方法:getImportGroup(),返回需要自定义排序的规则Group类
(如果实现了该接口方法,返回了非空的Group,那么就不会再调用ImportSelector中的selectImports方法)

  1. AutoConfigurationImportSelector实现了DeferredImportSelector的getImportGroup()方法,返回AutoConfigurationGroup.class实例

  2. AutoConfigurationGroup中定义了process方法和selectImports方法,首先process会加载所有在jar包文件夹中META-INF/spring.factories配置数据,然后调用Group实现的selectImports方法时再根据指定的@Order、@AutoConfigureBefore、 @AutoConfigureAfter规则排序,返回排序后的BeanName信息逐一注入Spring容器

总的来说核心流程可以看下图:

image.png

解析源码的原理,并不是说直接看懂每一个方法就完了,这样只是可以学会一些写代码的技巧和作者的整体思路,我们还可以在作者的角度思考,逐一参透其奥秘,比如:为什么用ImportSelector不行,为什么要延迟的DeferredImportSelector之类等。

小节

SpringBoot的自动装配实际上类似于对所有框架进行做了一个抽象组件工厂,这些组件的核心配置类都会加载进来,因为组件并不是都是全部需要的,引入了条件注解筛选这部分需要的组件,最后条件注解也会引入新的问题:条件一定要最后判断,于是就有了DeferredImportSelector的实现。

Springboot的自动装配原理到此就结束,相信看了这篇实现的思想,自己也能自定义实现一个自动装配组件,代码的细节这里就在文中指出了相关核心类。下一篇将继续总结SpringBoot的启动原理部分

  • 本文作者: 沧澜
  • 本文链接: https://www.meetxiyu.cn/archives/SpringBoot核心思想及源码解析(上)——自动装配
  • 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!
# 软件思想 # SpringBoot # 领域驱动设计 # 算法 # 中间件 # 计算机网络 # MySQL # 数据库 # javascript # 极客时间 # 分布式架构 # Jenkins # JVM # 多线程 # Java基础 # CentOS安装 # 编译OpenJDK # 持续集成 # 杂谈
聊聊ConcurrentHashMap的执行内幕(下)
领域驱动设计(DDD)到底在说什么——软件设计(一)
  • 文章目录
  • 站点概览
沧澜

沧澜

芝兰生于幽谷,不以无人而不芳
君子修身养德,不以穷困而改志

74 日志
19 分类
19 标签
RSS
Creative Commons
0%
© 2019 — 2026 蜀ICP备19039166号
由 Halo 强力驱动
|
主题 - NexT.Mist v5.1.4