SpringBoot源码学习3——SpringBoot启动流程
一丶前言
在 《SpringBoot源码学习1——SpringBoot自动装配源码解析+Spring如何处理配置类的》中我们学习了SpringBoot自动装配如何实现的,在 《Spring源码学习笔记12——总结篇IOC,Bean的生命周期,三大扩展点》我们总结了Spring IOC的底层原理。
但是我们还是不知道SpringApplication.run(主类.class, args)
到底做了哪些事情。本文将和大家一起看看SpringBoot启动的大致流程,探讨SpringBoot留给我们的扩展接口
二丶SpringBoot启动流程分析
上面是SpringBoot调用SpringApplication.run(主类.class, args)
启动的源码,源码并不复杂,整体流程大概如下
下面我们依据此图,看看这些步骤SpringBoot底层源码
1.获取SpringApplicationRunListener
实现类,包装成SpringApplicationRunListeners
SpringApplicationRunListener
是SpringBoot框架中的监听器,在SpringBoot启动到达对应阶段的时候,会回调starting
,started
等方法。为什么SpringBoot不适应Spring 里面的
ApplicationListener
昵,因为ApplicationListener
依赖于Spring容器,@EventListener
注解需要EventListenerMethodProcessor
这个BeanFactoryPostProcessor
扫描,将对应的bean和方法包装成ApplicationListener
注册到ApplicationContext
中(最终注册到ApplicationEventMulticaster
事件多播器中)对于ApplicationListener
类型bean则直接走注册到ApplicationContext
的流程,整个流程只有Spring 容器启动后才能进行,如果没有SpringApplicationRunListener
则开发者无法在SpringBoot启动对应阶段进行一些扩展逻辑的回调。SpringApplicationRunListeners
可以看成是SpringApplicationRunListener
的门面(门面设计模式)
其使用List<SpringApplicationRunListener>
持有所有的SpringApplicationRunListener
,然后starting
等方法都是循环调用,集合中SpringApplicationRunListener
对应的方法
SpringBoot如何获取所有的
SpringApplciationListener
这里将从
META-INF/spring.factories
获取org.springframework.boot.SpringApplicationRunListener
定义的实现类全限定类名,然后反射调用构造方法(SpringApplication application, String[] args)
进行实例化。随后将根据@Order
或者Ordered
接口定义的顺序进行排序,然后包装成SpringApplicationRunListeners注意无法使用@Component注解 标注在
SpringApplciationListener
注解上,来实现事件的监听,必须在META-INF/spring.factories
中定义,并且必须具备构造方法(SpringApplication application, String[] args)
。EventPublishingRunListener
SpringApplication#addListeners
允许我们注册ApplicationListener
到SpringBoot中,然后EventPublishingRunListener
其内部会new 一个简单的事件多播器SimpleApplicationEventMulticaster
,在对应的SpringBoot启动阶段,推送事件。下面式如何注册ApplicationListener注意这些ApplicationListener不会被注册到Spring上下文中,意味着不会响应Spring上下文推送的事件,除非这个ApplicationListener是一个Spring Bean 并且被Spring管理。
下图是
EventPublishingRunListener
在SpringBoot启动的不同阶段,推送事件
2.SpringApplicationListeners#starting
没啥好说的,循环回调SpringApplicationRunListener#starting
方法
3.prepareEnvironment 根据项目选择Environment实现类,并实例化
在这一步,SpringBoot会根据类路径中的类选择一个Environment
并实例化,并且根据当前激活的配置,选择对应的配置文件,进行解析,并保存到Environment
中。下面是SpringBoot选择Environment的源码
那么SpringBoot是如何判断当前项目是什么应用类型昵?
其实根据类路径下是否具备指定的类,然后得到指定类型,一般我们都是servlet应用,会选择StandardServletEnvironment
4.SpringApplicationListeners#environmentPrepared
同2.SpringApplicationListeners#starting
5.createApplicationContext
根据类路径指定类推断使用什么ConfigurableApplicationContext
(一般servlet应用使用AnnotationConfigServletWebServerApplicationContext)然后实例化AnnotationConfigServletWebServerApplicationContext
AnnotationConfigServletWebServerApplicationContext#onRefresh
方法在Spring容器刷新后会被调用,这个方法将启动Tomcat内嵌服务器
6.prepareContext
这个方法主要会做以下操作
- 回调
ApplicationContextInitializer#initialize
- 回调所有
SpringApplicationRunListener#contextPrepared
- 将主类包装成
BeanDefinition
,注册到Spring容器上下文中 - 回调所有
SpringApplicationRunListener#contextLoaded
利用SpringApplicationRunListeners
回调SpringApplicationRunListener
,同2,不在赘述
6.1从META-INFO/spring.factories中拿所有ApplicationContextInitializer
然后回调initialize方法
在spring上下文refresh方法调用前,会回调initialize
方法
这里调用前还会判断ApplicationContextInitializer
定义的泛型,保证5这一步创建的上下文,符合泛型的要求
6.2 将主类包装成BeanDefinition
,注册到Spring容器上下文中
这一步非常重要,主类上的注解@SpringBootApplication
需要ConfigurationClassPostProcessor
解析,才能发挥@Import,@ComponentScan的作用,想要ConfigurationClassPostProcessor
处理主类的前提是主类的BeanDefinition需要在Spring容器中。
也就是说SpringBoot的自动装配,和扫描包路径下的Spring 组件的前提是,主类的BeanDefinition在Spring容器中
这里的BeanDefinitionRegistry,其实就是来自5这一步的ApplicationContext,一般来说AnnotationConfigServletWebServerApplicationContext
内部持有了一个DefaultListableBeanFactory
,DefaultListableBeanFactory
是BeanDefinitionRegistry
的实现类,其底层使用一个ConcurrentHashMap
维护,key是bean的名称,value是对应的BeanDefinition
当资源是一个Class
的时候,会使用AnnotatedBeanDefinitionReader
读取Class
对象,生成BeanDefinition
这一步还支持xml的方式
7.回调SpringApplicationRunListener#contextLoaded
同2
8.刷新Spring容器上下文
《Spring源码学习笔记12——总结篇IOC,Bean的生命周期,三大扩展点》这篇博客做了详细的分析
这里会进行自动装配和包路径扫描注册BeanDefinition,然后实例化单例bean
9.回调SpringApplicationRunListener#started
同2
10.callRunners
从spring容器中拿到ApplicationRunner,和CommandLineRunner调用run方法
三丶SpringApplication,ApplicationContext,BeanFactory 三平面
我们将SpringApplication看作是SpringBoot平面,ApplicationContext看作是Spring平面,BeanFactory看作是Bean工厂平面,SpringBoot启动到触发spring容器刷新,然后触发BeanFactory实例化所有单例,非懒加载bean的流程如下
SpringBoot源码学习3——SpringBoot启动流程的更多相关文章
- SpringBoot源码学习1——SpringBoot自动装配源码解析+Spring如何处理配置类的
系列文章目录和关于我 一丶什么是SpringBoot自动装配 SpringBoot通过SPI的机制,在我们程序员引入一些starter之后,扫描外部引用 jar 包中的META-INF/spring. ...
- SpringBoot源码学习系列之启动原理简介
本博客通过debug方式简单跟一下Springboot application启动的源码,Springboot的启动源码是比较复杂的,本博客只是简单梳理一下源码,浅析其原理 为了方便跟源码,先找个Ap ...
- ASP.NET Core MVC 源码学习:MVC 启动流程详解
前言 在 上一篇 文章中,我们学习了 ASP.NET Core MVC 的路由模块,那么在本篇文章中,主要是对 ASP.NET Core MVC 启动流程的一个学习. ASP.NET Core 是新一 ...
- ThinkPHP5.0源码学习之框架启动流程
ThinkPHP5框架的启动流程图如下: ThinkPHP5的启动流程按照文件分为三步: 1.请求入口(public/index.php) 2.框架启动(thinkphp/start.php) 3.应 ...
- SpringBoot源码学习系列之嵌入式Servlet容器
目录 1.博客前言简单介绍 2.定制servlet容器 3.变换servlet容器 4.servlet容器启动原理 SpringBoot源码学习系列之嵌入式Servlet容器启动原理 @ 1.博客前言 ...
- SpringBoot源码分析之SpringBoot的启动过程
SpringBoot源码分析之SpringBoot的启动过程 发表于 2017-04-30 | 分类于 springboot | 0 Comments | 阅读次数 SpringB ...
- SpringBoot源码学习系列之异常处理自动配置
SpringBoot源码学习系列之异常处理自动配置 1.源码学习 先给个SpringBoot中的异常例子,假如访问一个错误链接,让其返回404页面 在浏览器访问: 而在其它的客户端软件,比如postm ...
- Cocos2dx源码赏析(1)之启动流程与主循环
Cocos2dx源码赏析(1)之启动流程与主循环 我们知道Cocos2dx是一款开源的跨平台游戏引擎,而学习开源项目一个较实用的办法就是读源码.所谓,"源码之前,了无秘密".而笔者 ...
- 【spring-boot 源码解析】spring-boot 依赖管理梳理图
在文章 [spring-boot 源码解析]spring-boot 依赖管理 中,我梳理了 spring-boot-build.spring-boot-parent.spring-boot-depen ...
- JVM源码分析之JVM启动流程
原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 “365篇原创计划”第十四篇. 今天呢!灯塔君跟大家讲: JVM源码分析之JVM启动流程 前言: 执行Java类的main方法,程序就能运 ...
随机推荐
- 重新整理 .net core 实践篇 ———— linux 上线篇 [外篇]
前言 简单整理一个linux 简单上线. 这个是该系列的外篇,该系列继续更新.献给刚学的人. 正文 安装实例 dotnet new webapp -n AspNetCoreDemo -o firstw ...
- Linux正则表达式与grep
bash是什么 bash是一个命令处理器,运行在文本窗口中,并能执行用户直接输入的命令 bash还能从文件中读取linxu命令,称之为脚本 bash支持通配符.管道.命令替换.条件判断等逻辑控制语句 ...
- Scrapy 如何传递 get请求的params
我们都知道 在requests中可以使用 requests.get(url,params)的方式传值 那么在scrapy中如何传值呢 直接看代码 from urllib.parse import ur ...
- 前端html和css总结
1.html知识总结 1.1 表格的的相关属性 属性 表示 border-collapse 设置表格的边框是否被合并为一个单一的边框 cellpadding 单元格边距 cellspacing 单元格 ...
- JAVA代码审计之xss
java_sec_code xss 补充和回忆一下一些开发基础 @RestController @RequestMapping(value = "/xss") public cla ...
- Mybatis:解决调用带有集合类型形参的mapper方法时,集合参数为空或null的问题
此文章有问题,待修改! 使用Mybatis时,有时需要批量增删改查,这时就要向mapper方法中传入集合类型(List或Set)参数,下面是一个示例. // 该文件不完整,只展现关键部分 @Mappe ...
- 【Devexpress】Gridcontorl动态创建列不显示的问题
通过代码创建列但是不显示,这个原因是因为代码创建的列Visible属性默认是false 所以需要设置为true就会显示了 gridColumn.Visible = true;
- 动态规划篇——DP问题
动态规划篇--DP问题 本次我们介绍动态规划篇的DP问题,我们会从下面几个角度来介绍: 区间DP 计数DP 树状DP 记忆化搜索 区间DP 我们通过一个案例来讲解区间DP: /*题目展示*/ 题目名: ...
- java基础知识-lambda表达式
一.什么是lambda? 在Java中,我们可以将一个值赋值给一个Java变量. int aValue = 129; String aString = "hello world"; ...
- 函数调用时用const保护指针
当调用函数并且把指向变量的指针作为参数传入时,通常会假设函数将修改变量(否则,为什么函数需要指针呢?).例如,如果在程序中看到语句 f(&x); 大概是希望f改变x的值.但是,f仅需检查x的值 ...