SpringBoot源码学习1——SpringBoot自动装配源码解析+Spring如何处理配置类的
一丶什么是SpringBoot自动装配
SpringBoot通过SPI的机制,在我们程序员引入一些starter之后,扫描外部引用 jar 包中的META-INF/spring.factories
文件,将文件中配置的类型信息加载到 Spring 容器,实现引入starter即可开启相关功能的操作,大大简化了程序员手动配置bean,即开即用。
二丶SpringBoot自动装配源码解析
1.源码解析入口
SpringApplication.run(启动类.class, args)
这是我们最常用的Main方法启动SpringBoot服务的方式,其中启动类上需要标注@SpringBootApplication
注解,自动装配,扫描主类下所有Bean的奥秘就在此
2.@SpringBootApplication注解
@SpringBootApplication
本身没有什么神奇的地方,重要的是注解上面标注了@SpringBootConfiguration
,@EnableAutoConfiguration
,和@ComponentScan
注解
@SpringBootConfiguration
平平无奇,上面标注了@Configuration
表示标注的类是一个配置类@EnableAutoConfiguration
表示开启自动配置,即我们说的SpringBoot自动装配、
@AutoConfigurationPackage
其上方标注了@Import(AutoConfigurationPackages.Registrar.class)
,加上@EnableAutoConfiguration
上的@Import(AutoConfigurationImportSelector.class)
.@Import
注解的作用是导入一些bean到Spring容器中,实现此功能的是ConfigurationClassPostProcessor
,它是一个BeanFactoryPostProcessor
会解析配置类中的@Bean,@Import,@ComponentScan等注解
@ComponentScan
,指导Spring容器需要扫描哪些包下的类加入到Spring容器
也就是说@SpringBootApplication
相当于
3.自动配置源码学习前言
SpringBoot并不是Spring的替代品,而是利用Spring加上约定大于配置
的思想,方便程序员开发的框架。所以其底层还是Spring那一套(Spring源码相关博客:Spring源码学习笔记12——总结篇,IOC,Bean的生命周期,三大扩展点)
本篇着重研究SpringBoot的自动装配原理,所以一些无关的代码不会进行详细探究,后续会单独学习整理。
4.SpringApplication#run是如何初始化ApplicationContext的
既然SpringBoot是基于Spring的,那么必然是无法脱离ApplicationContext的,接下来我们以SpringApplication#run
为入口看看,SpringApplication是如何初始化一个ApplicationContext的
4.1 SpringApplication的初始化
我们在启动类的main方法中SpringApplication.run(启动类.class, args)
其实最终调用的是
在其构造方法中:
根据当前项目判断Web应用类型
初始化
ApplicationContextInitializer
,和ApplicationListener
这部分是通过读
META-INF/spring.factories
中的内容反射进行初始化,前者是用于在刷新之前初始化 Spring ConfigurableApplicationContext 的回调接口,后者是Spring监听器,后续会进行专门的学习。获取主类
会new出一个
RuntimeException
,然后分析StackTraceElement
找到方法名称为main
,然后获取类名
springboot启动源码 自动配置需要关注的部分框出
4.2 根据项目创建一个合适的ApplicationConext
这里便是通过web应用的类型,反射生成AnnotationConfigServletWebServerApplicationContext
类型的上下文,也就是说,如果当前项目中存在Servlet
,和ConfigurableWebApplicationContext
那么SpringBoot会选择AnnotationConfigServletWebServerApplicationContext
其中ServletWebServerApplicationContext
具备启动Serlvet服务器(如Tomcat)并将Servlet 类型的bean或过滤器类型的 bean 都注册到 Web 服务器的能力
AnnotationConfigServletWebServerApplicationContext
则是在ServletWebServerApplicationContext
上增加了根据类路径扫描,注册Component到上下文的能力
4.3 刷新ApplicationContext的前置准备
在prepareContext
方法中,SpringBoot会把主类注册到Spring容器中,为什么要这么做昵,——主类上的注解@SpringBootApplication
需要ConfigurationClassPostProcessor
解析,才能发挥@Import,@ComponentScan的作用,想要ConfigurationClassPostProcessor
处理主类的前提是主类的BeanDefinition需要在Spring容器中
这里的BeanDefinitionRegistry即是AnnotationConfigServletWebServerApplicationContext
中持有的DefaultListableBeanFactory
如果是CharSequence
类型,会尝试使用Class.forName
解析成类,然后尝试使用解析Resouce,解析的Package的方式处理。
这里使用AnnotatedBeanDefinitionReader
注册我们的主类,此类在spring源码学习笔记2——基于注解生成BeanDefinition的过程解析中学习过。
简单来说就是会将主类的信息包装成AnnotatedGenericBeanDefinition
,其中会解析@Scope
,@Lazy
,@Primary
,@DependsOn
等注解设置到AnnotatedGenericBeanDefinition
中,然后调用BeanDefinitionCustomizer#customize
允许我们自定义处理BeanDefinition。
4.4 刷新ApplicationContext
这里就是调用AnnotationConfigServletWebServerApplicationContext#refresh
方法,来到AbstractApplicationContext
的refresh
方法中,执行流程如下
这里我们需要注意调用BeanFactoryPostProcessor,因为这里将调用到ConfigurationClassPostProcessor
,接下来我们将分析其源码,看看它究竟做了什么
4.4.1解析配置类中的相关注解
这一步发生在BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry
中
4.4.1.1遍历所有的BeanDefinition进行筛选
可以看到如果需要处理,会放入到集合中,那么什么样的类才需要进一步处理昵,首先这个类的BeanDefinition需要存在于Spring容器中
- 具备
@Configuration
注解,会被标记为了full
模式 - 具备
@Component
,@ComponentScan
,@Import
,@ImportResource
其中任何一个注解,会被标记为lite
模式 - 具备一个方法标注了
@Bean
注解,会被标记为lite
模式
full
和lite
的区别后面会将
获取候选者后会根据其@Order
注解中的顺序进行排序,SpringBoot项目通常这时候只有主类
4.4.1.2 使用ConfigurationClassParser
解析候选者
在这里SpringBoot的启动类,会被解析,
首先是进行条件注解解析,如果不符合条件那么什么都不做。这里的条件注解指的是
@Conditional
及其复合注解@ConditionOnClass
,@ConditionOnBean
等进行解析
这里循环当前类和其父类调用
doProcessConfigurationClass
进行解析,需要注意的是:如果父类上的Condition注解不满足,但是子类满足,但是子类是一个配置类,父类中的@Bean等注解,还是会进行解析如果标注了
@Component
及其复合注解那么解析内部类ConfigurationClassParser
会把当前配置类中的内部类也当作配置类解析,也就是说如果A是一个配置类候选者,内部类没有@Component,@Configuration也会当做配置进行解析解析
@PropertySources
和@PropertySource
ConfigurationClassParser
会将@PropertySources
指定的配置,加入到Environment
中解析
@ComponentScans
和@ComponentScan
这一步会确认条件注解中的内容满足,然后使用
ComponentScanAnnotationParser
,获取指定的路径,如果没有指定任何路径那么使用当前配置所在的路径,这也是为什么SpringBoot主类上没有指定扫描路径,但是默认加载主类所在包下所有类。扫描包路径下的所有类,使用指定的TypeFilter
进行过滤(检查是否具备@Component注解)且条件注解满足才会注册对应的BeanDefinition到容器中,这里SpringBoot指定了AutoConfigurationExcludeFilter
,其作用是排除掉扫描到的自动装配类,因为自动装配类由@Import(AutoConfigurationImportSelector.class)
导入的AutoConfigurationImportSelector
来处理扫到的类,还会当前配置类进行解析,如果是一个配置类即满足
- 具备
@Configuration
注解,会被标记为了full
模式 - 具备
@Component
,@ComponentScan
,@Import
,@ImportResource
其中任何一个注解,会被标记为lite
模式 - 具备一个方法标注了
@Bean
注解,会被标记为lite
模式
任何一个条件 那么会再次处理,有点递归的意思
- 具备
处理
@@Import
注解获取类上面的
@Import
注解内容导入的类是
ImportSelector
类型反射实例化ImportSelector
如果此
ImportSelector
实现了BeanClassLoaderAware
,BeanFactoryAware
,EnvironmentAware
,EnvironmentAware
,ResourceLoaderAware
会回调对应的方法调用当前
ImportSelector
的selectImports
,然后递归执行处理@Import
注解的方法,也就是说可以导入一个具备@Import
的类,如果没有``@Import`那么当中配置类解析导入的类是
ImportBeanDefinitionRegistrar
类型反射实例化
ImportBeanDefinitionRegistrar
,然后加入到importBeanDefinitionRegistrars
集合中后续会回调其registerBeanDefinitions
既不是
ImportBeanDefinitionRegistrar
也不是ImportSelector
,将导入的类当做配置类处理,后续会判断条件注解是否满足,然后解析导入的类,并且解析其父类
这一步便会解析到 @Import(AutoConfigurationImportSelector.class)进行自动装配,具体操作后续讲解
处理
@ImportResource
注解获取注解中指定的路径资源,和指定的
BeanDefinitionReader
类型,然后包装到importedResources
集合中,后续回调BeanDefinitionReader#loadBeanDefinitions
(默认使用XmlBeanDefinitionReader
),也就是说我们可以使用@ImportResource
导入一些定义在xml中的bean处理标注
@Bean
注解的方法会扫描标注
@Bean
的方法,存到beanMethods
集合中,后续解析方法上的条件注解,如果满足条件,将包装成ConfigurationClassBeanDefinition
,其中bean名称和别名来自@Bean中name指定,并且指定其factoryMethodName
,后续实例化bean的时候将反射调用标注的方法生成bean,然后解析@Lazy
,@Primary
,@DependsOn
等注解,还会解析@Bean注解中标注的是否依赖注入候选者,初始化方法,销毁方法,以及@Scope
注解,然后注册到BeanDefinitionRegistry中处理接口中标注@Bean的默认方法
获取当前类实现的全部的接口,且非抽象的方法,然后进行
6.处理标注
@Bean注解的方法
4.4.2增强配置类
在ConfigurationClassPostProcessor#postProcessBeanFactory
方法中,会对full
模式的配置进行增强,full模式指标注@Configuration注解的类,调用其enhanceConfigurationClasses
方法,拦截@Bean
方法,以确保正确处理@Bean
语义。Spring将使用CGLIB对原配置进行增强,获取增强后的类,替换调用原BeanDefinition记录的类,后续将使用此加强类,这也做的目的在于,调用配置类标注了@Bean方法的时候,不会真正调用其中的逻辑,而是直接取BeanFactory#getBean
中取,保证@Bean标注的方法,产生bean的生命周期完整
4.5 自动装配源码解析
上面关于ConfigurationClassPostProcessor
类的源码解析,我们明白了Spring是如何解析一个配置类的,其中和SpringBoot自动装配关系最密切的是对@Import
注解,SpringBoot启动上标注的@SpringBootApplication
包含了@Import(AutoConfigurationImportSelector.class)
,下面我们将解析AutoConfigurationImportSelector
到底做了什么来实现自动装配
首先我们可以通过spring.boot.enableautoconfiguration
来设置是否开启自动配置,那怕再配置类上面标注了@EnableAutoConfiguration
也可以进行关闭。
然后会先读取spring-autoconfigure-metadata.properties ,此文件存储的是”待自动装配候选类“过滤的计算规则,会根据里面的规则逐一对候选类进行计算看是否需要被自动装配进容器,并不是全部加载
然后是读取META-INF/spring.factories
中org.springframework.boot.autoconfigure.EnableAutoConfiguration
对应的自动配置类,如
首先使用classLoader读
META-INF/spring.factories
中org.springframework.boot.autoconfigure.EnableAutoConfiguration
对应的内容,然后进行去重然后获取自动装配注解标注的
exclude
和excludeName
表示不需要进行自动装配的类,并排除掉这些类然后获取
META-INF/spring.factories
中org.springframework.boot.autoconfigure.AutoConfigurationImportFilter
对应的内容,实例化成AutoConfigurationImportFilter
调用其match
方法,判断这些自动装配类是否需要被过滤掉,这是springboot留给我们的一个扩展点,如果需要读取缓存中的内容进行对自动配置类的过滤,我们可以自己实现一个AutoConfigurationImportFilter
放在META-INF/spring.factories
中,如org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=com.a.xx
即可进行自定义过滤紧接着会发送一个
AutoConfigurationImportEvent
事件关于SpringBoot的事件会在下一篇中讲解
最后会把需要自动装配的类全限定类名返回,接着就到了
ConfigurationClassPostProcessor
中,它会继续使用ConfigurationClassParser
将这些自动配置类进一步解析
有了这些知识,我们可以写一个自己的starter了(脑子:你回了。手:不,我不会)
SpringBoot源码学习1——SpringBoot自动装配源码解析+Spring如何处理配置类的的更多相关文章
- Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签
写在前面 上文Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作中Spring对XML解析后创建了对应的Docum ...
- SpringBoot自动装配-源码分析
1. 简介 通过源码探究SpringBoot的自动装配功能. 2. 核心代码 2.1 启动类 我们都知道SpringBoot项目创建好后,会自动生成一个当前模块的启动类.如下: import org. ...
- SpringBoot:认认真真梳理一遍自动装配原理
前言 Spring翻译为中文是“春天”,的确,在某段时间内,它给Java开发人员带来过春天,但是随着我们项目规模的扩大,Spring需要配置的地方就越来越多,夸张点说,“配置两小时,Coding五分钟 ...
- 你来说一下springboot的启动时的一个自动装配过程吧
前言 继续总结吧,没有面试就继续夯实自己的基础,前阵子的在面试过程中遇到的各种问题陆陆续续都会总结出来分享给大家,这次要说的也是面试中被问到的一个高频的问题,我当时其实没答好,因为很早之前是看到spr ...
- 天天用SpringBoot居然还不知道它的自动装配的原理?
引言 最近有个读者在面试,面试中被问到了这样一个问题"看你项目中用到了springboot,你说下springboot的自动配置是怎么实现的?"这应该是一个springboot里面 ...
- Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作
写在前面 上文 Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件主要讲Spring容器创建时通过XmlBeanDefinitionReader读 ...
- SpringBoot自动装配源码解析
序:众所周知spring-boot入门容易精通难,说到底spring-boot是对spring已有的各种技术的整合封装,因为封装了所以使用简单,也因为封装了所以越来越多的"拿来主义" ...
- SpringBoot启动代码和自动装配源码分析
随着互联网的快速发展,各种组件层出不穷,需要框架集成的组件越来越多.每一种组件与Spring容器整合需要实现相关代码.SpringMVC框架配置由于太过于繁琐和依赖XML文件:为了方便快速集成第三 ...
- SpringBoot自动装配源码
前几天,面试的时候被问到了SpringBoot的自动装配的原理.趁着五一的假期,就来整理一下这个流程. 我这里使用的是idea创建的最简单的SpringBoot项目. 我们都知道,main方法是jav ...
随机推荐
- S32Kxxx bootloader之UDS bootloader
了解更多关于bootloader 的C语言实现,请加我Q扣: 1273623966 (验证信息请填 bootloader),欢迎咨询或定制bootloader(在线升级程序). 两周前完成了基于UDS ...
- 面试官:Redis 过期删除策略和内存淘汰策略有什么区别?
作者:小林coding 计算机八股文网站:https://xiaolincoding.com 大家好,我是小林. Redis 的「内存淘汰策略」和「过期删除策略」,很多小伙伴容易混淆,这两个机制虽然都 ...
- 使用docker buildx打包发布多平台镜像
2022-07-07 个人比较喜欢影视作品,下载了大量的剧集视频,有些文件的命名不规范,就需要重新命名,之前是用的一款 renamer 客户端软件,用起来不太爽就自己做了个 bs 架构的重命名软件并开 ...
- 【python】自动更新pu口袋校园活动
[python]自动更新pu口袋校园活动 脚本目标: 1. 自动爬取pu口袋校园活动,筛选出需要的活动,此处我的筛选条件是线上活动,因为可以不用去就可以白嫖学时 2. 自动发送邮件到QQ邮箱,每次只发 ...
- 《深入理解java虚拟机》读书笔记-第二章Java内存区域和内存溢出异常
java1.7和java8的jvm存在差异,本文先按照<深入理解java虚拟机>的讲解内容总结,并将java8的改变作为附录放在文末 一丶运行时数据区域 图:java虚拟机运行时数据区 ...
- javaWeb,web服务器
一. 1.ASP 国内最早最流行的语言就是ASP:微软研发 在HTML中嵌套了VB脚本,ASP+COM(网页元素) 在ASP开发中,基本一个业务就有几千行代码,页面机器混乱 维护成本高 <h1& ...
- 请问为啥计算器16进制FFFFFFFFFFFF时10进制是-1?
请问为啥计算器16进制FFFFFFFFFFFF时10进制是-1?
- Keep In Line_via牛客网
题目 链接:https://ac.nowcoder.com/acm/contest/28537/H 来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 262144K,其他语 ...
- 在docker容器中如何自动生成配置文件(以nginx配置为例)
应用场景类似于多个域名要起多个容器,有些参数有些域名需要,有些域名不需要,或者参数的值不太一样,需要去对应的配置文件修改,不太灵活,如果通过变量的方式直接定义在Dockerfile文件中,需要哪些参数 ...
- 项目开发中Maven的单向依赖-2022新项目
一.业务场景 工作多年,在真实的项目开发中经常会遇到将一个项目拆分成多个工程的情况,比如将一个真实的项目拆分成controller层,service层, dao层,common公共服务层等等.这样拆分 ...