1、目的:

从源码的角度分析整个springboot的启动流程、了解springboot项目在整个启动过程都干了一些什么。
因为spring项目的启动主要是分为两步,一个是bean definition的获取,一个是根据bean definition 生成bean实例。
本篇主要涉及bean definition的获取部分,也就是第一点。
其中 bean definition的来源包括

(1)框架硬编码、(2)用户定义非mapper的bean、(3)通过自动配置加载xxAutoConfiguration、(4)mapper对象 。

本篇主要讲解的是2.3两个。

2、项目结构

基本的东西都是有的,包括dao,service、model、且包含mybatis框架(代码会在后面的链接)

3、加载bean definition的方法调用链

图片地址https://www.processon.com/view/link/5e847d98e4b07b16dcdb8a46

4、从启动类开始

首先从启动类开始分析

@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class, MybatisAutoConfiguration.class},scanBasePackages = {"com.defire.**"})
@ImportResource(locations = {"classpath:spring-jdbc-zx-lsq.xml"
})public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}}

这就是一个普通的main方法,里面调用了  SpringApplication.run 方法、传入启动类 DemoApplication作为参数。中间一些纯属转发的方法我们就不细看了。

找到第一个重点

 /*** 运行spring 应用,创造&刷新一个新的ApplicationContext*/public ConfigurableApplicationContext run(String... args) {....初始化环境Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();....省略ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);Banner printedBanner = printBanner(environment);//打印我们常见的那边spring bannercontext = createApplicationContext();//初始化AnnotationConfigServletWebServerApplicationContext 包括里面工厂对象。prepareContext(context, environment, listeners, applicationArguments,printedBanner);refreshContext(context);//最核心的,初始化整个spring环境,包括beandefinition的扫描,后置处理器的调用,bean的实例&初始化等几乎所有spring的核心都在这个方法里面afterRefresh(context, applicationArguments);...省略listeners.started(context);callRunners(context, applicationArguments);return context;}

接下来跟进的重要方法是  context对象的初始化方法(createApplicationContext)和prepareContext方法 和刷新容器的方法(refreshContext方法),特别是refreshContext 方法是特别复杂。

4、createApplicationContext()方法分析

这个是为了创建context对象。因为是通过反射的方式调用的构造器,所以我们直接找到目标类,直接在构造器里面打上断点。

实例化context的时候有哪些操作呢?

初始化AnnotatedBeanDefinitionReader的时候,里面有一个比较重要的就是初始化了一些负责处理注解的后置处理器。

包括@autowire注解、@required注解等

注意这里的factory 类型是DefaultListableBeanFactory。这个的beandefinition 只是bean的中间对象,没有实例化成为bean。

5、prepareContext()方法分析

这个方法不是很重要,但还是有几点需要提下,比如

  • A、DemoApplication(启动类) 的beanDefinition 收集完成,这属于第一种beandefinition收集方式,硬编码的收集方式。
  • B、bean容器(registeredSingletons )中首次加入4个实例化好bean对象 0 = "autoConfigurationReport" 1 = "org.springframework.boot.context.ContextIdApplicationContextInitializer$ContextId" 2 = "springApplicationArguments"  3 = "springBootBanner"  ,
  • C、context 加入了两个针对beanFactory 处理的后置处理器 0 = {ConfigurationWarningsApplicationContextInitializer$ConfigurationWarningsPostProcessor@4409}  1 = {SharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessor@4410}

这些都不是重点,这里就不展开了。对于prepareContext的方法的分析就告一段落。

6、refreshContext ()/refresh()

接下来要分析的就是refreshContext ,这个终极大boss,6.1,6.2 ....都是讨论这个方法里面的细节refreshContext的方法核心就是调用AbstractApplicationContext的refresh方法。中间会有一些跳转,我们直接讲核心方法。先来段代码。后面会针对这里面的方法一一展开。

@Override
public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {//一些准备工作,列如配置属性源,记录启动时间,环境变量准备,部分map的清理工作prepareRefresh();//能对xml读取,转换为bean,AbstractApplicationContext因为引用了一个beanFactory//所有她拥有了beanFactory的所有功能。还在此基础上进一步扩展。做了哪些扩展?//真正的初始化bean Factory ,设置一些参数,并读取xml文件,转换为bean DefinitionConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// 准备bean 工厂,对beanFactory进行各种功能填充// Prepare the bean factory for use in this context.// 此时,spring已经完成对配置文件的解析,也就是beanFactory对bean的解析已经开始prepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.//保留方法 允许在上下文子类中对bean工厂进行后处理。postProcessBeanFactory(beanFactory);// 激活各种beanFactory 处理器,按顺序执行了invokeBeanFactoryPostProcessors(beanFactory);// 执行完invokeBeanFactoryPostProcessors后,beanDefinition 基本都已经找出来了 ,并且新初始了如下 8个 bean 至此已经初始化16个bean// 注册拦截bean 创建的 bean 后置处理器  ,执行是在getBean时候// Register bean processors that intercept bean creation.// 注册 BeanPostProcessor 处理 bean的registerBeanPostProcessors(beanFactory);// 执行完 registerBeanPostProcessors()方法后,新增了9个后置处理器 至此已经初始化25个bean// 为此上下文初始化消息源,即不同语言的消息体。国际化处理// Initialize message source for this context.initMessageSource();//执行完新增1个bean 至此已经初始化26个bean//messageSource -> {DelegatingMessageSource@5542}//初始化应用消息(事件)广播器,并放入 ApplicationEventMulticaster bean 中// Initialize event multicaster for this context.// 广播器,会保存相关的监听器,在有事件发生的时候,会调用相关的监听器的方法,// 让监听器去执行内部的监听逻辑initApplicationEventMulticaster();//执行完新增一个bean 至此已经初始化27个bean//applicationEventMulticaster -> {SimpleApplicationEventMulticaster@5595}//留给子类来初始化其他bean// Initialize other special beans in specific context subclasses.onRefresh();//执行完 onRefresh()新增 29个 bean 至此已经初始化65 个bean// 将所有监听器bean,注册到前面刚刚注册的消息广播器。// 并将没有发出的事件一并发出去。// Check for listener beans and register them.registerListeners();// 初始化剩下的,非延迟加载的 bean的初始化工作。(也就是非延迟加载的bean,会在这一步实现初始化,其他bean不会))// ConversionService 设置(这个是干啥的?)// 配置冻结 、非延迟加载的 bean的初始化工作// Instantiate all remaining (non-lazy-init) singletons.//这也是context&bean Factory的区别所在finishBeanFactoryInitialization(beanFactory);// 发布相关事件 Last step: publish corresponding event// 完成刷新过程,通知生命周期处理器,LifecycleProcesser 刷新过程,// 并同时发出ContextRefreshEvent 通知别人(通知被人干啥.finishRefresh();}}

6.1:postProcessBeanFactory()

AbstractApplicationContext 的 postProcessBeanFactory 方法在父类AbstractApplicationContext是空,所以默认会寻找子类中同名方法,为啥要提到这点呢?这中情况在spring中有挺多的,父类中的方法是空,实际调用子类中同名方法,这个也叫做委派设计模式。

我们看下AbstractApplicationContext的子类都有哪些操作?

至此,AbstractApplicationContext 的 postProcessBeanFactory ()方法分析完毕。增加了一个bean的后置处理器。

6.2 invokeBeanFactoryPostProcessors()方法,

从方法名字上看就知道是调用一遍beanFactory的后置处理器。从堆栈中看到我们context对象保存了三个beanFactory的后置处理器,看下面的截图

至于这些都是干什么的,下面的分析中会包含。

6.2.1、第一次postProcessBeanDefinitionRegistry() 执行,

从三个后置处理器中拿出类型是BeanDefinitionRegistryPostProcessor的处理器,调用他们的postProcessBeanDefinitionRegistry方法。其中 ConfigurationWarningsApplicationContextInitializer 主要是对配置做一点检查工作,比如检查配置的扫描包是否正确;SharedMetadataReaderFactoryContextInitializer 主要是初始化了一个元数据读取器工厂,并将其设置到容器的configurationProcessor后置处理器中,也是为后序整个容器完全初始化做一些前期准备工作;下图就是原码截图。

第三个 ConfigFileApplicationContextInitializer并不属于BeanDefinitionRegistryPostProcessor 也就没有postProcessBeanDefinitionRegistry方法。

重点第一波执行的factory 后置处理器主要来源于beanFactoryPostProcessors属性内。接下来介绍的第二波调用的后置处理器来源不同了。虽然调用了好几次后置处理器,但是都不会重复,因为随着前一次调用结束,我们系统中又多出了一些尚未调用的后置处理器。

6.2.2、第二次postProcessBeanDefinitionRegistry() 执行,

第二次执行完的成果主要是解析出用户自己的bean definition和可以动态导入的bean definition,用户自己的比较好理解,就是业务定义的,可以动态导入的就看当前classpath路径的情况,系统给你导出。

首先从beandefinitionMap中取出类型是BeanDefinitionRegistryPostProcessor的对象。尽管整个容器目前已经包含了8个bean定义,但能属于BeanDefinitionRegistryPostProcessor类型的仅有一个internalConfigurationAnnotationProcessor(class是ConfigurationClassPostProcessor)

下面进入ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法,想必是处理bean注册的吧。果然我们看到最核心的方法是 processConfigBeanDefinitions() ,这里最核心的就是下面两条调用链了。第一条是处理业务自己定义的bean definition,第二条、spring框架智能导入一些bean。

postProcessBeanDefinitionRegistry()-->processConfigBeanDefinitions()--->parse()---->parse()--->processConfigurationClass()--->doProcessConfigurationClass() ;
postProcessBeanDefinitionRegistry()-->processConfigBeanDefinitions()--->parse()---->processDeferredImportSelectors()--->loadBeanDefinitions()

第一条链最核心的逻辑还是doProcessConfigurationClass 方法,该方法的主要操作包括
  • 处理注解 @ComponentScan
  • 处理 @Import 注解
  • 处理@ImportResource注解
  • 处理 带@Bean注解的 方法
  • .....

完成上面这个doProcessConfigurationClass方法后,用户定义的带有@component注解类的基本都转换为bean Definition 了。
下面重点分析分析第二步(处理注解 @ComponentScan)

第二步:处理注解 @ComponentScan

  • 1在配置类上找到ComponentScan 注解的所有属性
  • 2创建一个ClassPathBeanDefinitionScanner 调用其doScan方法对路径进行扫描
  • 3执行findCandidateComponents方法,主要目标是找出所有带有@component注解,mapper等接口暂时是不会扫描进来
  • 4然后执行 postProcessBeanDefinition ,对扫描到的bean Definition进一步补充资料(设置一些默认值,比如懒加载、init方法等)
  • 5再执行 processCommonDefinitionAnnotations处理一些公共注解的填充工作。上一步是设置默认的,如果用户有指定,则会在这一步替换为用户指定值。

第2点(创建一个ClassPathBeanDefinitionScanner 调用其doScan方法对路径进行扫描)截图

第5点(再执行 processCommonDefinitionAnnotations 处理一些公共注解的填充工作。上一步是设置默认的,如果用户有指定,则会在这一步替换为用户指定值)源码如下

static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);if (lazy != null) {abd.setLazyInit(lazy.getBoolean("value"));//设置lazy属性}else if (abd.getMetadata() != metadata) {lazy = attributesFor(abd.getMetadata(), Lazy.class);if (lazy != null) {abd.setLazyInit(lazy.getBoolean("value"));//设置lazy-init属性}}if (metadata.isAnnotated(Primary.class.getName())) {abd.setPrimary(true);//设置primary属性}.......}

执行 registerBeanDefinition,到这个阶段bean Definition 基本初始化完成,可以注册到容器中,等待bean的实例化阶段来使用即可。注册bean的过程其实就是将beanDefinition 放入容器内。如下图

行 registerBeanDefinition,到这个阶段bean Definition 基本初始化完成,可以注册到容器中,等待bean的实例化阶段来使用即可。注册bean的过程其实就是将beanDefinition 放入容器内。如下图

分析完第一条调用链的逻辑之后(处理用户自己的bean definition)我们接下来分析第二条获取 bean definition的调用链。

先大体浏览下第二条调用链的方法调用栈。

第二条链主要是从几个配置文件中读取一些相关配置类,进行一些过滤之后加载到系统中。我们先找到加载配置文件的方法,selectImport方法。下面是源码

 @Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;}AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);//拿到配置类的元数据AnnotationAttributes attributes = getAttributes(annotationMetadata);List<String> configurations = getCandidateConfigurations(annotationMetadata,attributes);configurations = removeDuplicates(configurations);//去除重复的配置类Set<String> exclusions = getExclusions(annotationMetadata, attributes);//去除用户指定的不包含的配置类checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);configurations = filter(configurations, autoConfigurationMetadata);//按照条件进一步过滤fireAutoConfigurationImportEvents(configurations, exclusions);return StringUtils.toStringArray(configurations);}

先从这个配置文件中读取全部配置信息。我们看下配置信息都是什么样的。

可以看到都是一堆的AutoConfiguration类,这个就是可以理解为一个功能的配置类,比如http消息转换的配置类,在他里面会引入一些其他类。 processDeferredImportSelectors方法执行完成之后,这些bean definition还没有实例化。真正实例化的时候是后面的  this.reader.loadBeanDefinitions(configClasses);

至此

本篇主要目标 对bean definition的获取部分的理解已经基本讲完,其中bean definition的来源包括

(1)框架硬编码、(2)用户定义非mapper的bean、(3)通过自动配置加载xxAutoConfiguration、(4)mapper对象 。

这里主要讲解的是2.3两个。

SpringBoot的Bean Definition 生成过程(源码分析)相关推荐

  1. springboot自动配置文件读取以及源码分析

    今天来讲讲springboot自动配置文件读取以及源码分析 springboot启动之后 1.首先进入@springbootApplication(如上图) 里面的**@EnableAutoConfi ...

  2. 【Spring】Bean生命周期源码分析 总结

    [Spring]Bean生命周期源码总结 1.案例验证 定义两个bean A,B 以及实现MyBeanFactoryProcess,MyBeanProcessor,MyInstantiationAwa ...

  3. spring boot实战(第十篇)Spring boot Bean加载源码分析

    前言 前面的文章描述了Application对应Bean的创建,本篇将阐述spring boot中bean的创建过程 refresh 首先来看SpringApplication#run方法中refre ...

  4. kubernetes pod-name生成过程 源码分析

    kubernetes 版本 [root@master-47-35 ~]# kubectl version Client Version: version.Info{Major:"1" ...

  5. SpringBoot @ConditionalOnBean、@ConditionalOnMissingBean注解源码分析与示例

    前言: Spring4推出了@Conditional注解,方便程序根据当前环境或者容器情况来动态注入bean 继@Conditional注解后,又基于此注解推出了很多派生注解,比如@Condition ...

  6. 【深入设计模式】单例模式—从源码分析内部类单例、枚举单例以及单例模式在框架中的应用

    文章目录 1. 使用静态内部类实现单例模式 1.1 静态内部类单例写法 1.2 如何实现懒加载 1.3 为什么线程安全 2. 枚举类型单例单例模式 2.1 枚举类型单例写法 2.2 枚举类型单例原理 ...

  7. Springboot源码分析第一弹 - 自动装配实现

    Springboot就不用多了吧,解放Java开发双手的神器. 最显著的特点就是,去配置化,自动装配,自动配置.让开发人员只需要注重业务的开发 今天就来了解一下自动装配的源码是怎么实现的 预先准备 直 ...

  8. SpringBoot自定义异常源码分析

    SpringBoot自定义异常源码分析 在类上加ControllerAdvice注解,在方法上加ExceptionHandler注解,就可以在方法里处理相应的异常. 1.自定义异常处理类Additio ...

  9. springboot 事务_原创002 | 搭上SpringBoot事务源码分析专车

    前言 如果这是你第二次看到师长,说明你在觊觎我的美色! 点赞+关注再看,养成习惯 没别的意思,就是需要你的窥屏^_^ 专车介绍 该趟专车是开往Spring Boot事务源码分析的专车 专车问题 为什么 ...

最新文章

  1. GlusterFS下如何修复裂脑文件?(续一)
  2. python时间重叠_python-检测重叠的日期重复规则
  3. B/S和C/S的区别
  4. 澳洲 计算机 本科学费,澳大利亚墨尔本大学一年学费和生活费清单
  5. arduino定时器函数如何使用_Excel表格技巧—如何使用DELTA 函数
  6. 说说 Spring AOP 原理
  7. SpringBoot 整合Shiro Ehcache
  8. 2019 最烂密码排行榜大曝光!网友:已中招!
  9. shell逻辑判断、文件属性判断、if特殊用法、case判断
  10. 阿里云ACE认证之理解CDN技术 1
  11. python游戏解法图_python 游戏(记忆拼图Memory_Puzzle)
  12. Pandas中的数据聚合方法
  13. 三八定律时间管理思想
  14. Javascript:简易天数计算器
  15. Java获取URL对应的资源
  16. 深圳大数据学习:泛型 --【千锋】
  17. 2008年北京奥运会:出现可能性极高的十句话
  18. android 自动语音,在Android上自动下载离线语音识别语言
  19. 如何成为月薪5W的数据产品经理?
  20. 苏州大学老师腾讯会议忘记关共享画面不忍直视...

热门文章

  1. 中国公认的大学生计算机编程第一人:楼天城
  2. Kdevelop使用
  3. redis 交集、并集、差集
  4. java equals和==的区别
  5. 【K8S专栏】Kubernetes工作负载管理
  6. 阿里云 IoT 企业物联网平台 MQTT 通讯模式
  7. IDEA中如何设置键盘快捷键可用
  8. RabbitMQ 网页端控制台开启方式
  9. pd.read_excel出现xlrd.biffh.XLRDError: Excel xlsx file; not supported解决方案
  10. C# Winform 置顶属性Topmost 的误区