springboot启动类源码探索一波
举个例子:


这是一个原始的Spring IOC容器启动方法,我们需要AnnotationConfigApplicationContext这个类有如下几个步骤
1. 创建构造方法,根据我们所传入的AppConfig配置类上的@ComponentScan(value ="com.zhongyu")得到扫描路径
2. 扫描该路径下的所有带@Component注解的类,这里说一下Spring的懒加载机制,默认Spring只会扫描生成没有懒加载注解的单例Bean,
原型Bean(每次调用都重新生成一个Bean)是不加载的,所以在这一步Spring需要判断@Component或者@Bean修饰的类,
将扫描到的类解析成BeanDefinition(Bean定义),存入BeanDefinitionMap,在BeanDefinitionMap判断Bean是不是单例Bean,
如果是那么就确认需要在启动时生成Bean,通知BeanFactory根据我们的BeanDefinition生成我们需要的Bean,
注意这里扫描的不是我们的源代码路径,而是扫描的编译过后的class文件路径,在这一步做了很多路径上的转换比如\\转.
3. 后面的流程大致是无参构造方法->创建普通对象->填充属性->初始化前->初始化->初始化后(AOP)->代理对象->Bean的一个过程
源码如何切分?
SpringApplication中的静态run()方法并不是一步完成的,最终执行的源码如下:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
很显然分为两个步骤,分别是创建SpringApplication和执行run()方法,下面将分为这两个部分介绍。
如何创建SpringApplication?
设置应用类型
这个过程非常重要,直接决定了项目的类型,应用类型分为三种,都在WebApplicationType这个枚举类中,如下:
NONE:顾名思义,什么都没有,正常流程走,不额外的启动web容器,比如Tomcat。SERVLET:基于servlet的web程序,需要启动内嵌的servletweb容器,比如Tomcat。REACTIVE:基于reactive的web程序,需要启动内嵌reactiveweb容器,作者不是很了解,不便多说。
判断的依据很简单,就是加载对应的类,比如加载了DispatcherServlet等则会判断是Servlet的web程序。源码如下:
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
这里我引入了spring-boot-starter-web,肯定是Servlet的web程序。
设置初始化器(Initializer)
初始化器ApplicationContextInitializer是个好东西,用于IOC容器刷新之前初始化一些组件,比如ServletContextApplicationContextInitializer。
我们自定义一个ApplicationContextInitializer只需要实现接口,在spring.factories文件中设置即可。
设置监听器(Listener)
监听器(ApplicationListener)这个概念在Spring中就已经存在,主要用于监听特定的事件(ApplicationEvent),比如IOC容器刷新、容器关闭等。
Spring Boot扩展了ApplicationEvent构建了SpringApplicationEvent这个抽象类,主要用于Spring Boot启动过程中触发的事件,比如程序启动中、程序启动完成等。如下图:

总结:
SpringApplication的构建都是为了run()方法启动做铺垫,构造方法中总共就有几行代码,最重要的部分就是设置应用类型、设置初始化器、设置监听器。
「注意」:初始化器和这里的监听器都要放置在
spring.factories文件中才能在这一步骤加载,否则不会生效,因此此时IOC容器还未创建,即使将其注入到IOC容器中也是不会生效的。

执行run()方法
上面分析了SpringApplication的构建过程,一切都做好了铺垫,现在到了启动的过程了。
作者根据源码将启动过程分为了「8步」,下面将会一一介绍。
1. 获取、启动运行过程监听器
SpringApplicationRunListener这个监听器和ApplicationListener不同,它是用来监听应用程序启动过程的,接口的各个方法含义如下:
public interface SpringApplicationRunListener {
// 在run()方法开始执行时,该方法就立即被调用,可用于在初始化最早期时做一些工作
void starting();
// 当environment构建完成,ApplicationContext创建之前,该方法被调用
void environmentPrepared(ConfigurableEnvironment environment);
// 当ApplicationContext构建完成时,该方法被调用
void contextPrepared(ConfigurableApplicationContext context);
// 在ApplicationContext完成加载,但没有被刷新前,该方法被调用
void contextLoaded(ConfigurableApplicationContext context);
// 在ApplicationContext刷新并启动后,CommandLineRunners和ApplicationRunner未被调用前,该方法被调用
void started(ConfigurableApplicationContext context);
// 在run()方法执行完成前该方法被调用
void running(ConfigurableApplicationContext context);
// 当应用运行出错时该方法被调用
void failed(ConfigurableApplicationContext context, Throwable exception);
}
如何获取运行监听器?
在SpringApplication#run()方法中,源码如下:
//从spring.factories中获取监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
跟进getRunListeners()方法,其实还是调用了loadFactoryNames()方法从spring.factories文件中获取值,如下:
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
最终注入的是EventPublishingRunListener这个实现类,创建实例过程肯定是通过反射了,因此我们看看它的构造方法,如下图:

这个运行监听器内部有一个事件广播器(SimpleApplicationEventMulticaster),主要用来广播特定的事件(SpringApplicationEvent)来触发特定的监听器ApplicationListener。EventPublishingRunListener中的每个方法用来触发SpringApplicationEvent中的不同子类。
如何启动运行监听器?
在SpringApplication#run()方法中,源码如下:
//执行starting()方法
listeners.starting(bootstrapContext, this.mainApplicationClass);
执行SpringApplicationRunListeners的starting()方法,跟进去其实很简单,遍历执行上面获取的运行监听器,这里只有一个EventPublishingRunListener。
因此执行的是它的starting()方法,源码如下图:

上述源码中逻辑很简单,其实只是执行了multicastEvent()方法,广播了ApplicationStartingEvent事件。至于multicastEvent()内部方法感兴趣的可以看看,其实就是遍历ApplicationListener的实现类,找到监听ApplicationStartingEvent这个事件的监听器,执行onApplicationEvent()方法。
总结
这一步其实就是广播了ApplicationStartingEvent事件来触发监听这个事件的ApplicationListener。
因此如果自定义了ApplicationListener并且监听了ApplicationStartingEvent(应用程序开始启动)事件,则这个监听器将会被触发。
2. 环境构建
这一步主要用于加载系统配置以及用户的自定义配置(application.properties),源码如下,在run()方法中:
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
prepareEnvironment方法内部广播了ApplicationEnvironmentPreparedEvent事件,源码如下图:
环境构建这一步加载了系统环境配置、用户自定义配置并且广播了ApplicationEnvironmentPreparedEvent事件,触发监听器。
3. 创建IOC容器
源码在run()方法中,如下:
context = createApplicationContext();

根据webApplicationType决定创建的类型,很显然,我这里的是servlet,因此创建的是AnnotationConfigServletWebServerApplicationContext。这一步仅仅是创建了IOC容器,未有其他操作。
4. IOC容器的前置处理
这一步真是精华了,在刷新容器之前做准备,其中有一个非常关键的操作:将启动类注入容器,为后续的自动化配置奠定基础。源码如下:
prepareContext(context, environment, listeners, applicationArguments,printedBanner)
调用初始化器
在SpringApplication构建过程中设置的初始化器,从spring.factories取值的。执行的流程很简单,遍历执行,
将自定义的ApplicationContextInitializer放在META-INF/spring.factories中,在此时也是会被调用。
加载启动类,注入容器
这一步是将主启动类加载到IOC容器中,作为后续自动配置的入口。
在SpringApplication构建过程中将主启动类放置在primarySources这个集合中,此时的getAllSources()即是从其中取值,如下图:

这里取出的就是主启动类,当然你的项目中可能不止一个,接下来就是将其加载到IOC容器中了,源码如下:
load(context, sources.toArray(new Object[0]));
跟着代码进去,其实主要逻辑都在BeanDefinitionLoader.load()方法
将主启动类加载到beanDefinitionMap,后续该启动类将作为开启自动配置化的入口,后续章节详细介绍。
两次广播事件
这一步涉及到了两次事件广播,分别是ApplicationContextInitializedEvent和ApplicationPreparedEvent,对应的源码如下:
listeners.contextPrepared(context);
load(context, sources.toArray(new Object[0]));
5. 刷新容器
刷新容器完全是Spring的功能了,比如初始化资源,初始化上下文广播器等,这个就不再详细介绍,有兴趣可以看看Spring的源码。
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
//调用创建的容器applicationContext中的refresh()方法
((AbstractApplicationContext)applicationContext).refresh();
}
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
/**
* 刷新上下文环境
*/
prepareRefresh();
/**
* 初始化BeanFactory,解析XML,相当于之前的XmlBeanFactory的操作,
*/
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
/**
* 为上下文准备BeanFactory,即对BeanFactory的各种功能进行填充,如常用的注解@Autowired @Qualifier等
* 添加ApplicationContextAwareProcessor处理器
* 在依赖注入忽略实现*Aware的接口,如EnvironmentAware、ApplicationEventPublisherAware等
* 注册依赖,如一个bean的属性中含有ApplicationEventPublisher(beanFactory),则会将beanFactory的实例注入进去
*/
prepareBeanFactory(beanFactory);
try {
/**
* 提供子类覆盖的额外处理,即子类处理自定义的BeanFactoryPostProcess
*/
postProcessBeanFactory(beanFactory);
/**
* 激活各种BeanFactory处理器,包括BeanDefinitionRegistryBeanFactoryPostProcessor和普通的BeanFactoryPostProcessor
* 执行对应的postProcessBeanDefinitionRegistry方法 和 postProcessBeanFactory方法
*/
invokeBeanFactoryPostProcessors(beanFactory);
/**
* 注册拦截Bean创建的Bean处理器,即注册BeanPostProcessor,不是BeanFactoryPostProcessor,注意两者的区别
* 注意,这里仅仅是注册,并不会执行对应的方法,将在bean的实例化时执行对应的方法
*/
registerBeanPostProcessors(beanFactory);
/**
* 初始化上下文中的资源文件,如国际化文件的处理等
*/
initMessageSource();
/**
* 初始化上下文事件广播器,并放入applicatioEventMulticaster,如ApplicationEventPublisher
*/
initApplicationEventMulticaster();
/**
* 给子类扩展初始化其他Bean
*/
onRefresh();
/**
* 在所有bean中查找listener bean,然后注册到广播器中
*/
registerListeners();
/**
* 设置转换器
* 注册一个默认的属性值解析器
* 冻结所有的bean定义,说明注册的bean定义将不能被修改或进一步的处理
* 初始化剩余的非惰性的bean,即初始化非延迟加载的bean
*/
finishBeanFactoryInitialization(beanFactory);
/**
* 通过spring的事件发布机制发布ContextRefreshedEvent事件,以保证对应的监听器做进一步的处理
* 即对那种在spring启动后需要处理的一些类,这些类实现了ApplicationListener<ContextRefreshedEvent>,
* 这里就是要触发这些类的执行(执行onApplicationEvent方法)
* 另外,spring的内置Event有ContextClosedEvent、ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、RequestHandleEvent
* 完成初始化,通知生命周期处理器lifeCycleProcessor刷新过程,同时发出ContextRefreshEvent通知其他人
*/
finishRefresh();
}
finally {
resetCommonCaches();
}
}
}
6. IOC容器的后置处理
一个扩展方法,源码如下:
afterRefresh(context, applicationArguments);
默认为空,如果有自定义需求可以重写,比如打印一些启动结束日志等。
7. 发出结束执行的事件
同样是EventPublishingRunListener这个监听器,广播ApplicationStartedEvent事件。
但是这里广播事件和前几次不同,并不是广播给SpringApplication中的监听器(在构建过程中从spring.factories文件获取的监听器)。
因此在IOC容器中注入的监听器(使用@Component等方式注入的)也能够生效。前面几个事件只有在spring.factories文件中设置的监听器才会生效。
这里并没有用事件广播器SimpleApplicationEventMulticaster广播事件,而是使用ConfigurableApplicationContext直接在IOC容器中发布事件。
8. 执行Runners
Spring Boot 提供了两种Runner让我们定制一些额外的操作,分别是CommandLineRunner和ApplicationRunner,关于这两个的区别,后面文章详细介绍。
调用的源码如下:
callRunners(context, applicationArguments);
跟进代码,其实真正调执行的是如下方法:

逻辑很简单,从IOC容器中获取,遍历调用。
总结
Spring Boot 启动流程相对简单些,作者将其细分了以上八个步骤,希望能够帮助读者理解,流程图如下:

总结
Spring Boot启动流程就介绍到这里了,需要重点理解run()方法执行的八个步骤以及事件、初始化器、监听器等组件的执行时间点。
springboot启动类源码探索一波的更多相关文章
- SpringBoot启动tomcat源码解读
一.SpringBoot自动拉起Tomcat 原文链接:http://www.studyshare.cn/blog-front/blog/details/1136 SpringBoot框架是当前比较流 ...
- 带着萌新看springboot源码11(springboot启动原理 源码上)
通过前面这么多讲解,springboot原理应该也大概有个轮廓了,一些基本的配置,从客户端url到controller(配置一些要用的组件,servlet三大组件,处理器映射器,拦截器,视图解析器这些 ...
- SpringBoot启动注解源码流程学习总结
- Eureka源码探索(一)-客户端服务端的启动和负载均衡
1. Eureka源码探索(一)-客户端服务端的启动和负载均衡 1.1. 服务端 1.1.1. 找起始点 目前唯一知道的,就是启动Eureka服务需要添加注解@EnableEurekaServer,但 ...
- SpringBoot源码学习1——SpringBoot自动装配源码解析+Spring如何处理配置类的
系列文章目录和关于我 一丶什么是SpringBoot自动装配 SpringBoot通过SPI的机制,在我们程序员引入一些starter之后,扫描外部引用 jar 包中的META-INF/spring. ...
- Springboot自动装配源码及启动原理理解
Springboot自动装配源码及启动原理理解 springboot版本:2.2.2 传统的Spring框架实现一个Web服务,需要导入各种依赖JAR包,然后编写对应的XML配置文件 等,相较而言,S ...
- SpringBoot自动装配源码解析
序:众所周知spring-boot入门容易精通难,说到底spring-boot是对spring已有的各种技术的整合封装,因为封装了所以使用简单,也因为封装了所以越来越多的"拿来主义" ...
- Golang源码探索(三) GC的实现原理(转)
Golang从1.5开始引入了三色GC, 经过多次改进, 当前的1.9版本的GC停顿时间已经可以做到极短.停顿时间的减少意味着"最大响应时间"的缩短, 这也让go更适合编写网络服务 ...
- SpringBoot SpringApplication底层源码分析与自动装配
目录 抛出问题 @SpringBootApplication注解剖析 SpringApplication类剖析 第一步:配置SpringBoot Bean来源 第二步 :自动推断SpringBoot的 ...
- 基于SpringBoot的Environment源码理解实现分散配置
前提 org.springframework.core.env.Environment是当前应用运行环境的公开接口,主要包括应用程序运行环境的两个关键方面:配置文件(profiles)和属性.Envi ...
随机推荐
- keil 5 安装教程
一.下载 keil 官网 二.安装教程 1.开始安装 双击安装包,开始安装,直接下一步 2.勾选同意,下一步 3.选择软件安装路径,下一步 4.填写信息 可以随意填写,下一步. 5.等待安装 6.安装 ...
- jmeter--jsr223组件使用和功能详解
相比于BeanShell 取样器,JSR223取样器具有可大大提高性能的功能(编译)如果需要,一定要使用JSR223取样器编写脚本是更好的选择!!! 属性描述名称:显示的此取样器的描述性名称,可自定义 ...
- JPA动态注册多数据源
背景 目前已经是微服务的天下,但是随着业务需求的日益增长,部分应用还是出现了需要同时连接多个数据源操作数据的技术诉求. 需要对现有的技术架构进行优化升级,查阅了下网上的文章,基本都是照搬的同一篇文章, ...
- java中父类方法return this.对象还是变量,子类去调用this.这个方法的问题
这个问题很简单 public class this_test01 { public static void main(String[] args) { zi j=new zi(); j.pri(); ...
- 01 MyBatis第一个应用程序
1.MyBatis是什么? mybatis是一个基于java的持久层框架. 2.什么是持久化 数据由瞬态状态变为持久状态. 3.持久层: 完成持久化工作的代码块. -- DAO层,将数据存到数据库 4 ...
- 5分钟攻略Spring-Retry框架实现经典重试场景
前言 今天分享干货,控制了篇幅,5分钟内就能看完学会. 主题是Spring-Retry框架的应用,做了一个很清晰的案例,代码可下载自测. 框架介绍 Spring-Retry框架是Spring自带的功能 ...
- eclipse工具使用
eclipse下载 官网下载:https://www.eclipse.org/downloads/packages/ 打开后,找到Eclipse IDE for Java Developers点击进入 ...
- 微服务网关限流&鉴权-wei-fu-wu-wang-guan-xian-liu--jian-quan
title: 微服务网关限流&鉴权 date: 2022-01-06 14:40:45.047 updated: 2022-01-06 14:40:45.047 url: https://ww ...
- 如何从零开始实现TDOA技术的 UWB 精确定位系统(2)
这是一个系列文章<如何从零开始实现TDOA技术的 UWB 精确定位系统>第2部分. 重要提示(劝退说明): Q:做这个定位系统需要基础么?A:文章不是写给小白看的,需要有电子技术和软件编程 ...
- 2023年资深C#开发者的思考
2023年转眼间就这样过掉了,作为一名资深C#的开发员人员,年龄也大了1岁,从最早接触C#开始,算下来已经超过15年以上了,随着工作经验的不断增加,物价不断的飞涨以及家庭支出的不断上涨,工作1份工资已 ...