Spirng Boot2 系列教程(二十二)| 启动原理
一个读者,也是我的好朋友投稿的一篇关于 SpringBoot 启动原理的文章,才大二就如此优秀,未来可期。
我一直想了解一下 SpirngBoot 的是如何启动的,我想就来写一篇关于 SpirngBoot 启动分析吧。第一次写那么高深的技术话题理解不到位的话也请多多包涵。
源码版本
SpinrgBoot 2.0.2
众所周知 SpringBoot 的启动类是在一个 main 方法中调用 SpringApplication.run()
方法启动的,如:
@SpringBootApplication
public class DiveInSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(DiveInSpringBootApplication.class, args);
}
}
启动顺序分析如下:
初始化阶段 -> 运行阶段
初始化阶段:
进入run方法中,SpringApplication.run()
会先为其创建一个 SpringApplication 对象:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
//加载应用资源(URL资源、File资源、ClassPath资源)
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// primarySources 为 run 方法传入的引导类
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//推断Web应用类型
this.webApplicationType = deduceWebApplicationType();
//加载应用上下文(初始化Initializers)
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//加载应用事件监听器(初始化ApplicationListener)
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//推断引导类
this.mainApplicationClass = deduceMainApplicationClass();
}
Step1. 通过 deduceWebApplicationType()
来推断我们Web类型应用
private WebApplicationType deduceWebApplicationType() {
//根据当前应用的ClassPath中是否存在相关实现类来推断Web类型
if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
看看使用到的 3 个常量值:
常量值 | 应用类型 |
---|---|
REACTIVE_WEB_ENVIRONMENT_CLASS | org.springframework.web.reactive.DispatcherHandler |
MVC_WEB_ENVIRONMENT_CLASS | org.springframework.web.servlet.DispatcherServlet |
WEB_ENVIRONMENT_CLASSES | {"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" } |
也就是说,有如下三种情况:
如果应用程序中存在 org.springframework.web.reactive.DispatcherHandler 这个类,则表示是一个响应式 web 应用,项目在启动时,需要去加载启动内嵌的响应式 web 服务器。
如果应用程序中既不存在 javax.servlet.Servlet 类,也不存在org.springframework.web.context.ConfigurableWebApplicationContext 这个类,则
表示当前应用不是一个web应用,启动时无需加载启动内嵌的 web 服务器。除上述两种情况外,则表示当前应用是一个 servlet 的 web 应用,启动时需要加载启动内嵌的 servlet 的 web 服务器(比如 Tomcat )。
Step2. 如何加载应用上下文初始器(初始化 Initializers )
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
利用Spirng的工厂加载机制,实例化ApplicationContextInitializer实现类,并排序集合。具体实现方法如下:
- 通过
SpringFactoriesLoader.loadFactoryNames
来扫描META-INF/spring.factories
下符合 ApplicationContextInitializer 类型的资源名称。 - 实例化所有在
META-INF/spring.factories
下找到的资源信息 - 对实例化的资源信息进行优先级顺序排序,或通过
@order
注解和Ordered
接口进行排序
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
#Spring容器的常见的错误配置警告
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
#设置Spring应用上下文ID
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
Step3. 加载应用事件监听器( ApplicationListener )
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
利用 Spring 工厂加载机制,实例化 ApplicationListene r实现类,并排序对象集合,具体方法跟上面 初始化Initializers
类似,不赘述。
# Application Listeners
org.springframework.context.ApplicationListener=\
#Spring应用上下文加载完成之后清除缓存
org.springframework.boot.ClearCachesApplicationListener,\
#父容器关闭时通知各个子容器关闭,
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
#文件编码
org.springframework.boot.context.FileEncodingApplicationListener,\
#控制台彩色输出
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
#外部化配置 管理factories或者YMAL文件
org.springframework.boot.context.config.ConfigFileApplicationListener,\
#将指定事件广播给指定的监听器
org.springframework.boot.context.config.DelegatingApplicationListener,\
#将需要输出的日志打印到指定的级别 DEBUG INFO ERROR
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
#初始化日志系统
org.springframework.boot.context.logging.LoggingApplicationListener,\
#控制可执行Spirng文件版本
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
Step4. 推断引导类
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
//根据 Main 线程执行堆栈来判断实际的引导类。
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
运行阶段
整个 SpringApplication 围绕着 run 这个方法并分为两个小阶段:
- 加载
SpringApplication
运行监听器,并监听Spring Boot
事件 - 创建 Spring 应用上下文和创建 Environment
public ConfigurableApplicationContext run(String... args) {
//记录运行时间
StopWatch stopWatch = new StopWatch();
stopWatch.start();
//Spring 应用的上下文
ConfigurableApplicationContext context = null;
//记录启动期间的错误
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//配置文件加载及优先级判断
configureHeadlessProperty();
//获取SpringApplicationRunListeners
SpringApplicationRunListeners listeners = getRunListeners(args);
//加载运行监听器
listeners.starting();
try {
//创建ApplicationArguments对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//加载属性配置
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
//打印Banner
Banner printedBanner = printBanner(environment);
//创建应用上下文
context = createApplicationContext();
//实例化SpringBootExceptionReporter用于报告启动过程错误。
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//初始化应用上下文
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//刷新应用上下文(IOC容器的准备,初始化Bean)
refreshContext(context);
//应用上下刷新完成之后
afterRefresh(context, applicationArguments);
stopWatch.stop();
//启动日志记录器
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
//启动运行监听器
listeners.started(context);
//启动后需要的操作
callRunners(context, applicationArguments);
....
}
}
Step1. 加载SpringApplication
运行监听器
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
利用 Spirng 的工厂加载机制,实例化 SpringApplicationRunListeners 实现类,并排序集合。具体实现方法如下:
通过
SpringFactoriesLoader.loadFactoryNames
来扫描META-INF/spring.factories
下符合 SpringApplicationRunListeners 类型的资源名称。实例化所有在
META-INF/spring.factories
下找到的资源信息
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListenr
由此可见就只有 EventPublishingRunListenr
一个实现类
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
private final SpringApplication application;
private final String[] args;
private final SimpleApplicationEventMulticaster initialMulticaster;
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
//实例化SimpleApplicationEventMulticaster事件发布者
this.initialMulticaster = new SimpleApplicationEventMulticaster();
//以迭代的方法逐一进行ApplicationListener的监听
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
@Override
public int getOrder() {
return 0;
}
@Override
public void starting() {
this.initialMulticaster.multicastEvent(
new ApplicationStartingEvent(this.application, this.args));
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
this.application, this.args, environment));
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
for (ApplicationListener<?> listener : this.application.getListeners()) {
if (listener instanceof ApplicationContextAware) {
((ApplicationContextAware) listener).setApplicationContext(context);
}
context.addApplicationListener(listener);
}
this.initialMulticaster.multicastEvent(
new ApplicationPreparedEvent(this.application, this.args, context));
}
@Override
public void started(ConfigurableApplicationContext context) {
context.publishEvent(
new ApplicationStartedEvent(this.application, this.args, context));
}
@Override
public void running(ConfigurableApplicationContext context) {
context.publishEvent(
new ApplicationReadyEvent(this.application, this.args, context));
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
ApplicationFailedEvent event = new ApplicationFailedEvent(this.application,
this.args, context, exception);
if (context != null && context.isActive()) {
// Listeners have been registered to the application context so we should
// use it at this point if we can
context.publishEvent(event);
}
else {
// An inactive context may not have a multicaster so we use our multicaster to
// call all of the context's listeners instead
if (context instanceof AbstractApplicationContext) {
for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
.getApplicationListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
this.initialMulticaster.multicastEvent(event);
}
}
private static class LoggingErrorHandler implements ErrorHandler {
private static Log logger = LogFactory.getLog(EventPublishingRunListener.class);
@Override
public void handleError(Throwable throwable) {
logger.warn("Error calling ApplicationEventListener", throwable);
}
}
}
在 EventPublishingRunListener
实例化的时候,会实例化一个 SimpleApplicationEventMulticaster
事件发布者(它的作用就是监听容器中发布的事件,只要事件发生,就触发监听器的回调,来完成事件驱动开发),于是接下来调用 listeners.starting()
方法就会通过其内部的 initialMulticaster
属性发布 ApplicationStartingEvent
事件。
Step2. 创建Spirng应用上下文
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
//初始化阶段的推断Web类型
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
根据初始化阶段的推断Web应用类型来创建对应的 ConfigurableApplicationContext 实例
如果推断的为 SERVLETWeb 类型就实例化这个对象
web.servlet.context.AnnotationConfigServletWebServerApplicationContext
Step3. 创建 Environment
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
//创建 ConfigurableEnvironment 对象
ConfigurableEnvironment environment = getOrCreateEnvironment();
//配置 ConfigurableEnvironment
configureEnvironment(environment, applicationArguments.getSourceArgs());
//发布 ApplicationEnvironmentPreparedEvent 事件
listeners.environmentPrepared(environment);
//将 ConfigurableEnvironment 绑定到 SpringApplication 中
bindToSpringApplication(environment);
if (this.webApplicationType == WebApplicationType.NONE) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
根据初始化阶段的推断Web应用类型来创建对应的 ConfigurableEnvironment 实例。
至此整一个的 SpringBoot 过程已经分析完毕:我们来总结一下:
首先初始化 SpringApplication 类,并推断 WEB 启动类型,再初始化和实现应用事件监听器,然后推断引导类。
通过 SpringFactoriesLoader 加载的 SpringApplicationRunListener,调用它们的 started 方法。
根据 Web 服务类型创建不同的 Spring 应用上下文,并将之前准备好的 Environment 设置给 Spring 应用上下文 ApplicationContext 使用。
创建并配置当前 Spring Boot 应用将要使用的 Environment,如 applocation.properties 文件和外部配置。
SpirngBoot 开始启动。
推荐阅读
后语
如果看到这里,喜欢这篇文章的话,请转发、点赞。微信搜索「一个优秀的废人」,欢迎关注。
回复「1024」送你一套完整的 java、python、c++、go、前端、linux、算法、大数据、人工智能、小程序以及英语教程。
回复「电子书」送你 50+ 本 java 电子书。
Spirng Boot2 系列教程(二十二)| 启动原理的更多相关文章
- Spring Boot2 系列教程(三十二)Spring Boot 整合 Shiro
在 Spring Boot 中做权限管理,一般来说,主流的方案是 Spring Security ,但是,仅仅从技术角度来说,也可以使用 Shiro. 今天松哥就来和大家聊聊 Spring Boot ...
- Spring Boot2 系列教程(三十)Spring Boot 整合 Ehcache
用惯了 Redis ,很多人已经忘记了还有另一个缓存方案 Ehcache ,是的,在 Redis 一统江湖的时代,Ehcache 渐渐有点没落了,不过,我们还是有必要了解下 Ehcache ,在有的场 ...
- 【REACT NATIVE 系列教程之十二】REACT NATIVE(JS/ES)与IOS(OBJECT-C)交互通信
http://blog.csdn.net/xiaominghimi/article/details/51586492 一用到跨平台的引擎必然要有引擎与各平台原生进行交互通信的需要.那么Himi先讲解R ...
- 数据挖掘入门系列教程(十二)之使用keras构建CNN网络识别CIFAR10
简介 在上一篇博客:数据挖掘入门系列教程(十一点五)之CNN网络介绍中,介绍了CNN的工作原理和工作流程,在这一篇博客,将具体的使用代码来说明如何使用keras构建一个CNN网络来对CIFAR-10数 ...
- Linux系列教程(十二)——Linux软件包管理之yum在线管理
上一篇博客我们介绍了rpm包管理之rpm命令管理,我们发现在使用rpm命令手动安装rpm包的时候,会发现安装遇到到的依赖让你痛不欲生,安装一个rpm时会要先先安装某个依赖的rpm,而安装这个依赖的rp ...
- guitar pro系列教程(十二):如何设置Guitar Pro的不完全小节
当我们新建一个GTP谱的时候,我们肯定是要用到节拍,是的,一个乐谱节拍设置的好不好,将直接影响你的乐谱效果好不好,设置节拍的步骤我们之前也有讨论过,今天主要跟大家讲的便是不完全小节. 不完全小节顾名思 ...
- Java NIO系列教程(十二) Java NIO与IO
当学习了Java NIO和IO的API后,一个问题马上涌入脑海: 我应该何时使用IO,何时使用NIO呢?在本文中,我会尽量清晰地解析Java NIO和IO的差异.它们的使用场景,以及它们如何影响您的代 ...
- FL studio系列教程(十二):FL Studio中如何导出音频
在FL Studio中制作好音乐后,最后展现给我们的是一般的音频文件,我们可以通过FL Studio的文件菜单导出最终的文件格式.下面我们就来详细的看一下FL Studio中是如何导出我们想要的音频格 ...
- Spring Boot2 系列教程(十九) | @Value 和 @ConfigurationProperties 的区别
微信公众号:一个优秀的废人.如有问题,请后台留言,反正我也不会听. 前言 最近有跳槽的想法,所以故意复习了下 SpringBoot 的相关知识,复习得比较细.其中有些,我感觉是以前忽略掉的东西,比如 ...
随机推荐
- Spring与C3p0连接数据库对事务操作
maven包: <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncodin ...
- 在vue项目中如何添加eslint
随着vue的越做越好,更多的开发者选择使用vue,本篇记录如何在vue项目中添加eslint. 首先第一种就是在vue项目创建初始时就选择了创建,随着初始化一起代入到了项目当中,那么要是一开始觉得es ...
- LA 3942 ——Trie (前缀树)、DP
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> ...
- ubuntu中桌面图标的配置
在网上随处可以找到怎么样把应用程序的图标放到桌面上,我刚用ubuntu时也是按照网上的做法,一步一步的做的,现将网上的做法复制下来: 桌面配置文件简述\label{sec:desktop file} ...
- Promise的封装
要封装Promise,首先要了解Promise的使用. Promise有以下几个特点:1.Promise是一个构造函数 2.实例化Promise时有两个回调函数,resolve,reject ,成功执 ...
- H3C 因特网域名结构树
- H3C 根据子网数划分子网
- H3C 子网划分方法
- artDialog4.0.5
artDialog4.0.5 2011-08-22 11:54:36 haiwei_sun 阅读数 4109 收藏 更多 分类专栏: jquery 下载 Google Code 项目主页 最新版 ...
- Linux 内核取消 urb
为停止一个已经提交给 USB 核心的 urb, 函数 usb_kill_urb 或者 usb_unlink_urb 应 当被调用: int usb_kill_urb(struct urb *urb); ...