SpringBoot执行原理
目录
一、执行原理:
每个Spring Boot项目都有一个主程序启动类,在主程序启动类中有一个启动项目的main()方法, 在该方法中通过执行SpringApplication.run()即可启动整个Spring Boot程序。
Q:
那么SpringApplication.run()方法到底是如何做到启动Spring Boot项目的呢?
@SpringBootApplication //能够扫描Spring组件并自动配置SpringBoot
public class Springboot01DemoApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01DemoApplication.class, args);
}
}
上述是一个SpringBoot的启动类,进入SpringApplication.run()方法

如图所示,进入了run方法后,紧接着,调用了重载方法,重载方法做了两件事:
- 实例化SpringApplication对象
- 调用run方法
1. 实例化SpringApplication对象
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
//......设置了一些参数....这里省略,下面是重点
//......设置了一些参数....这里省略,下面是重点
//......设置了一些参数....这里省略,下面是重点
//项目启动类 SpringbootDemoApplication.class设置为属性存储起来
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//设置应用类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 设置初始化器(Initializer),最后会调用这些初始化器
//所谓的初始化器就是org.springframework.context.ApplicationContextInitializer的实现类,在Spring上下文被刷新之前进行初始化的操作
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 设置监听器(Listener)
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 初始化 mainApplicationClass 属性:用于推断并设置项目main()方法启动的主程序启动类
this.mainApplicationClass = deduceMainApplicationClass();
}
SpringApplication的构造方法中,首先设置了一些参数,然后做了5件事:
1.1 项目启动类 SpringbootDemoApplication.class设置为属性存储起来
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
给这个成员变量赋值,把传入的primarySources进行转换,然后赋值,这个primarySources就是我们Springboot启动类的Main方法中传入的:

1.2 设置应用类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用
this.webApplicationType = WebApplicationType.deduceFromClasspath();
判断当前的web应用类型是servlet应用还是reactive应用,那么如何判断的? 进入.deduceFromClasspath()方法:

- 首先判断类路径下Reactive相关的class是否存在,如果存在就说明当前应用是内嵌的 Reactive Web 应用。例如说,Spring Webflux 。
- 判断类路径下是否存在Servlet类型的类。如果不存在,则返回
NONE,表示当前应用是非内嵌的 Web 应用。 - 否则,表示当前应用是内嵌的 Servlet Web 应用。例如说,Spring MVC 。
1.3 设置初始化器(Initializer),最后会调用这些初始化器
所谓的初始化器就是org.springframework.context.ApplicationContextInitializer的实现类,在Spring上下文被刷新之前进行初始化的操作.
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
这里传入了一个ApplicationContextInitializer.class
进入getSpringFactoriesInstances()方法(下图如果看不清请右键另存为):

这段代码主要做了如下几件事:
SpringFactoriesLoader.loadFactoryNames(type, classLoader)
这里的type就是刚才传入的,ApplicationContextInitializer.classloadFactoryNames调用了loadSpringFactories方法loadSpringFactories方法做了如下的事:Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
判断
classLoader是否为空,如果不为空加载META-INF下的spring.factories,如上图所示,根据传入的参数值(ApplicationContextInitializer.class)的类型,在spring.factories中进行查找,根据当前传入的类型找到两个类,这两个类就是初始化器:org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
得到这两个类后,把它们存入set去重,然后进行实例化,然后排序,最终返回,到此初始化器已经设置完成了。然后存入List<ApplicationContextInitializer<?>> initializers,等待之后使用

1.4 设置监听器(Listener)
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
和1.3同理,也是通过调用getSpringFactoriesInstances,只不过传递的参数发生了改变。变成了ApplicationListener.class ,所以它就是在spring.factories中根据ApplicationListener.class找,然后实例化,然后返回存入Listeners中。
1.5 初始化 mainApplicationClass 属性
用于推断并设置项目main()方法启动的主程序启动类
this.mainApplicationClass = deduceMainApplicationClass();
private Class<?> deduceMainApplicationClass() {
try {
// 获得当前 StackTraceElement 数组
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
// 判断哪个执行了 main 方法
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
} catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
判断哪个类执行了main方法,然后返回。
1.6 总结
实例化SpringApplication对象做了哪些事?
- 项目启动类 SpringbootDemoApplication.class设置为属性存储起来
- 设置应用类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)
- 设置初始化器(Initializer),最后会调用这些初始化器
- 设置监听器(Listener)
- 初始化 mainApplicationClass 属性:用于推断并设置项目main()方法启动的主程序启动类
2. 调用run方法
回忆一下,在SpringBoot启动类的Main方法中,执行了SpringApplication.run(Main方法所在的当前类.class, args);,这个方法主要做了两件事:
- 实例化SpringApplication对象 (已上述)
- 调用run方法
进入run方法:

run方法大体上做了9件比较重要的事。
2.1 获取并启动监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
//args是启动Spring应用的命令行参数,该参数可以在Spring应用中被访问。如:--server.port=9000
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

它其实还是通过getSpringFactoriesInstances()这个方法来获取,这个方法已经很熟悉了, 1.3,1.4都使用到了,不再赘述。
那么本步骤就是通过getSpringFactoriesInstances()拿到了一个SpringApplicationRunListeners类型的监听器,然后调用.starting()启动。
2.2 项目运行环境Environment的预配置
创建并配置当前SpringBoot应用将要使用的Environment,并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
Banner printedBanner = printBanner(environment);
进入prepareEnvironment()方法:

- 查询environment,有就返回,没有的话创建后返回。
- 配置环境
- PropertySources:加载执行的配置文件
- Profiles:多环境配置,针对不同的环境,加载不同的配置
- listeners环境准备(就是广播ApplicationEnvironmentPreparedEvent事件)。
- 将创建的环境绑定到SpringApplication对象上
- 是否是web环境,如果不是就转换为标准环境
- 配置PropertySources对它自己的递归依赖
- 返回
此时已经拿到了ConfigurableEnvironment 环境对象,然后执行configureIgnoreBeanInfo(environment),使其生效。
2.3 创建Spring容器
context = createApplicationContext();
// 获得异常报告器 SpringBootExceptionReporter 数组
//这一步的逻辑和实例化初始化器和监听器的一样,
// 都是通过调用 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化所有的异常处理类。
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);

根据 webApplicationType 类型,获得 ApplicationContext 类型,这里创建容器的类型 还是根据webApplicationType进行判断的,该类型为SERVLET类型,所以会通过反射装载对应的字节码,也就是AnnotationConfigServletWebServerApplicationContext。
然后通过getSpringFactoriesInstances()获得异常报告器。
2.4 Spring容器前置处理
这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);

这块会对整个上下文进行一个预处理,比如触发监听器的响应事件、加载资源、设置上下文环境等等。
2.5 刷新容器
refreshContext(context);

- IOC容器初始化
- 向JVM运行时注册一个关机钩子(函数),在JVM关机时关闭这个上下文,除非它当时已经关闭。(如果jvm变关闭了,当前上下文对象也可以被关闭了)
//TODO refresh方法在springioc章节中会有详细说明(挖个坑- - )。
2.6 Spring容器后置处理
afterRefresh(context, applicationArguments);

扩展接口,设计模式中的模板方法,默认为空实现。
如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理。
2.7 发出结束执行的事件通知
listeners.started(context);

2.8 执行Runners运行器
callRunners(context, applicationArguments);

用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序。
Runner 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务启动后执行一次。
Spring Boot提供了ApplicationRunner和CommandLineRunner两种服务接口
2.9 发布应用上下文就绪事件
listeners.running(context);
表示在前面一切初始化启动都没有问题的情况下,使用运行监听器SpringApplicationRunListener持续运行配置好的应用上下文ApplicationContext.
这样整个Spring Boot项目就正式启动完成了。
2.10 返回容器
return context;
完成~
总结:
- 获取并启动监听器
- 项目运行环境Environment的预配置
- 创建Spring容器
- Spring容器前置处理
- 刷新容器
- Spring容器后置处理
- 发出结束执行的事件通知
- 执行Runners运行器
- 发布应用上下文就绪事件
- 返回容器
SpringBoot执行原理的更多相关文章
- Springboot Actuator之八:actuator的执行原理
本文接着<Springboot Actuator之七:actuator 中原生endpoint源码解析1>,前面主要分析了原生endpoint的作用. 现在着重了解actuator的执行原 ...
- SpringBoot启动原理及相关流程
一.springboot启动原理及相关流程概览 springboot是基于spring的新型的轻量级框架,最厉害的地方当属自动配置.那我们就可以根据启动流程和相关原理来看看,如何实现传奇的自动配置 二 ...
- SpringBoot底层原理及分析
一,Spring Boot简介 1.什么是Spring Boot: SpringBoot是由Pivotal团队提供的框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程. 该框架使用了特 ...
- SpringBoot启动原理
SpringBoot启动原理 我们开发任何一个Spring Boot项目,都会用到如下的启动类: @SpringBootApplication public class Application { p ...
- 这一次搞懂SpringBoot核心原理(自动配置、事件驱动、Condition)
@ 目录 前言 正文 启动原理 事件驱动 自动配置原理 Condition注解原理 总结 前言 SpringBoot是Spring的包装,通过自动配置使得SpringBoot可以做到开箱即用,上手成本 ...
- 2 — springboot的原理
1.初步探索:第一个原理:依赖管理 发现:这里面存放着各种jar包 和 版本号 这也是:我们在前面第一个springboot项目创建中勾选了那个web,然后springboot就自动帮我们导入很多东西 ...
- Javascript之数据执行原理探究
Javascript在Web服务器端执行原理: 1.客户端请求数据,即我们在上网时在地址栏中输入某个网址,浏览器接收到数据之后,向远程web服务器发送请求报文. 2.web服务器响应请求,web服务器 ...
- Python程序的执行原理(转载)
Python程序的执行原理 2013-09-17 10:35 佚名 tech.uc 1. 过程概述 Python先把代码(.py文件)编译成字节码,交给字节码虚拟机,然后虚拟机一条一条执行字节码指令 ...
- smarty模板执行原理
为了实现程序的业务逻辑和内容表现页面的分离从而提高开发速度,php 引入了模板引擎的概念,php 模板引擎里面最流行的可以说是smarty了,smarty因其功能强大而且速度快而被广大php web开 ...
随机推荐
- 他是 ISIJ 第四名,也是在线知名题库的洛谷“网红”
转载自加藤惠. 2020年国际初中生信息学竞赛(ISIJ)上,以优秀成绩拿下第四名年仅初三的张湫阳,成为最夺目的选手之一. 而且虽然是初三的选手,但他取得优异成绩后,不少网友并不感到陌生,纷纷留言: ...
- mq消息
同步,异步,单向 Message的扩展属性主要包含下面几个: tag:消息tag,用于消息过滤 keys:Message索引键,用多个空格隔开,可以根据这些key快速检索到消息 waitStoreMs ...
- Linux学习进度记录(一)
一.按系列罗列Linux的发行版,并描述不同版本之间的联系和区别 1. RHEL (RedHat Enterprise Linux):红帽企业版Linux,红帽公司是全球最大的开源技术厂商,RHE ...
- CentOS 7下安装Docker
安装一些必要的系统工具: sudo yum install -y yum-utils device-mapper-persistent-data lvm2 添加软件源信息: sudo yum-conf ...
- JAVA在最新版Windows10_1909版本环境下的环境变量配置
1.配置 1.1新建 JAVA_HOME C:\Program Files\Java\jdk-13.0.2 1.2新建 CLASSPATH .;%JAVA_HOME%\bin;%JAVA_HOME%\ ...
- 软件工程与UML 第一次个人作业
这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzzcxy/2018SE1/ 这个作业要求在哪里 https://edu.cnblogs.com/campus/f ...
- Jmeter(三十一) - 从入门到精通 - Jmeter Http协议录制脚本工具-Badboy4(详解教程)
1.简介 上一篇文章中宏哥给小伙伴或童鞋们介绍讲解了手动添加Variable list的值,而实际工作中Badboy为我们提供了Variable setter工具,让我们不再使用哪一种比较笨拙的方法了 ...
- day6(celery配置与基本使用)
1.celery配置与基本使用 1.1 安装celery pip install celery @ https://github.com/celery/celery/tarball/master 1. ...
- 第11.9节 Python正则表达式的贪婪模式和非贪婪模式
在使用正则表达式时,匹配算法存在贪婪模式和非贪婪模式两种模式,在<第11.8节 Pytho正则表达式的重复匹配模式及元字符"?". "*". " ...
- 第15.20节 PyQt(Python+Qt)入门学习:QColumnView的作用及开发中对应Model的使用
老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 一.概述 在Qt Designer的Item Views(Model-based)部件中,Colum ...