我们知道,SpringBoot程序的启动很简单,代码如下:

@SpringBootApplication
public class Application { public static void main(String[] args){
SpringApplication.run(Application.class,args);
}
}

  调用SpringApplication的静态方法run,这个run方法会构造一个SpringApplication实例,然后这调用这个实例的run方法就能启动SpringBoot程序,因此如果要分析SpringBoot程序的启动过程,我们就要先分析SpringApplication实例的构造过程以及run方法的执行过程。

SpringApplication的构造过程

  下面是SpringApplication的静态run方法的源码,从源码可以得知,在执行SpringApplication的run方法的时候,先创建SpringApplication实例,然后载调用实例的run方法。

 public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return new SpringApplication(sources).run(args);
}

  在SpringApplication的构造函数中,调用私有的initialize方法,initialize方法的定义如下:

@SuppressWarnings({ "unchecked", "rawtypes" })
private void initialize(Object[] sources) {
if (sources != null && sources.length > ) {
this.sources.addAll(Arrays.asList(sources));
}
//判断是否是web程序,并设置到webEnvironment属性中
this.webEnvironment = deduceWebEnvironment();
//从spring.factories文件中找出key为ApplicationContextInitializer的类并实例化后设置到SpringApplication的initializers属性中。这个过程也就是找出所有的应用程序初始化器
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 从spring.factories文件中找出key为ApplicationListener的类并实例化后设置到SpringApplication的listeners属性中。这个过程就是找出所有的应用程序事件监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
找出main函数所在类
this.mainApplicationClass = deduceMainApplicationClass();
}

  getSpringFactoriesInstances最终的定义为:

private <T> Collection<? extends 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<String>(
//从META-INF/spring.factories文件中取出type类型的所有类名
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//实例化得到的类
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}

默认情况下,initialize方法从spring.factories文件中找出的key为ApplicationContextInitializer的类有:

0 = "org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer"
1 = "org.springframework.boot.context.ContextIdApplicationContextInitializer"
2 = "org.springframework.boot.context.config.DelegatingApplicationContextInitializer"
3 = "org.springframework.boot.context.embedded.ServerPortInfoApplicationContextInitializer"
4 = "org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer"
5 = "org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer"

key为ApplicationListener的有:

0 = "org.springframework.boot.ClearCachesApplicationListener"
1 = "org.springframework.boot.builder.ParentContextCloserApplicationListener"
2 = "org.springframework.boot.context.FileEncodingApplicationListener"
3 = "org.springframework.boot.context.config.AnsiOutputApplicationListener"
4 = "org.springframework.boot.context.config.ConfigFileApplicationListener"
5 = "org.springframework.boot.context.config.DelegatingApplicationListener"
6 = "org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener"
7 = "org.springframework.boot.logging.ClasspathLoggingApplicationListener"
8 = "org.springframework.boot.logging.LoggingApplicationListener"
9 = "org.springframework.boot.autoconfigure.BackgroundPreinitializer"

SpringApplication的执行

  在分析SpringApplication的run方法之前,先来看一下SpringApplicationRunListeners和SpringApplicationRunListener。SpringApplicationRunListeners内部持有一个SpringApplicationRunListener的集合,用于SpringApplicationRunListener监听的批量执行。SpringApplicationRunListener用于监听SpringApplication的run方法的执行。他定义了5个方法,代码如下:

public interface SpringApplicationRunListener {

    /**
*run方法执行的时候立马执行;对应事件的类型是ApplicationStartedEvent
*/
void starting(); /**
* ApplicationContext创建之前并且环境信息准备好的时候调用;对应事件的类型是ApplicationEnvironmentPreparedEvent
*/
void environmentPrepared(ConfigurableEnvironment environment); /**
* ApplicationContext创建好并且在source加载之前调用一次;没有具体的对应事件
*/
void contextPrepared(ConfigurableApplicationContext context); /**
* ApplicationContext创建并加载之后并在refresh之前调用;对应事件的类型是ApplicationPreparedEvent
*/
void contextLoaded(ConfigurableApplicationContext context); /**
*run方法结束之前调用;对应事件的类型是ApplicationReadyEvent或ApplicationFailedEvent
*/
void finished(ConfigurableApplicationContext context, Throwable exception);
}

  SpringApplicationRunListener目前只有一个实现类EventPublishingRunListener,它把监听的过程封装为SpringApplicationEvent,并通过自身的SimpleApplicationEventMulticaster属性把时间multicast出去,广播出去的事件对象会被SpringApplication中的listeners属性进行处理。所以说SpringApplicationRunListener和ApplicationListener之间的关系是通过ApplicationEventMulticaster广播出去的SpringApplicationEvent所联系起来的。

  下面我们看一下SpringApplication的run方法的源码:

public ConfigurableApplicationContext run(String... args) {
//开始任务执行时间监听器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
//设置系统属性『java.awt.headless』,为true则启用headless模式支持
configureHeadlessProperty();
//通过*SpringFactoriesLoader*检索*META-INF/spring.factories*,
//找到声明的所有SpringApplicationRunListener的实现类并将其实例化,
//之后逐个调用其starting()方法,广播SpringBoot要开始执行了。
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//创建并配置当前SpringBoot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile),
//并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法,广播Environment准备完毕。
ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
//决定是否打印Banner
Banner printedBanner = printBanner(environment);
//根据webEnvironment的值来决定创建何种类型的ApplicationContext对象
    //如果是web环境,则创建org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext
     //否则创建org.springframework.context.annotation.AnnotationConfigApplicationContext
context = createApplicationContext();
//注册异常分析器
analyzers = new FailureAnalyzers(context);
//为ApplicationContext加载environment,之后逐个执行ApplicationContextInitializer的initialize()方法来进一步封装ApplicationContext,
 //并调用所有的SpringApplicationRunListener的contextPrepared()方法,【EventPublishingRunListener只提供了一个空的contextPrepared()方法】,
//之后初始化IoC容器,并调用SpringApplicationRunListener的contextLoaded()方法,广播ApplicationContext的IoC加载完成,
//这里就包括通过**@EnableAutoConfiguration**导入的各种自动配置类。
prepareContext(context, environment, listeners, applicationArguments,printedBanner);
//初始化所有自动配置类,调用ApplicationContext的refresh()方法
refreshContext(context);
//遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法。
//该过程可以理解为是SpringBoot完成ApplicationContext初始化前的最后一步工作,
//我们可以实现自己的ApplicationRunner或者CommandLineRunner,来对SpringBoot的启动过程进行扩展。
afterRefresh(context, applicationArguments);
//调用所有的SpringApplicationRunListener的finished()方法,广播SpringBoot已经完成了ApplicationContext初始化的全部过程。
listeners.finished(context, null);
//关闭任务执行时间监听器
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
//调用异常分析器打印报告,调用所有的SpringApplicationRunListener的finished()方法将异常信息发布出去
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}

SpringBoot的启动过程,实际上就是对ApplicationContext的初始化过程。
ApplicationContext创建后立刻为其设置Environmen,并由ApplicationContextInitializer对其进一步封装。
通过SpringApplicationRunListener在ApplicationContext初始化过程中各个时点发布各种广播事件,并由ApplicationListener负责接收广播事件。
初始化过程中完成IoC的注入,包括通过@EnableAutoConfiguration导入的各种自动配置类。
初始化完成前调用ApplicationRunner和CommandLineRunner的实现类。

  

SpringBoot启动过程分析的更多相关文章

  1. 001-快速搭建Spring web应用【springboot 2.0.4】-gradle、springboot的启动过程分析、gradle多模块构建

    一.概述 学习<精通Spring MVC4>书籍笔记 二.笔记 1.快速构建Spring starter web项目几种方式 1>使用Spring Tool Suite生成Start ...

  2. SpringBoot启动过程原理

    最近这两年springboot突然火起来了,那么我们就来看看springboot的运行原理. 一.springboot的三种启动方式: 1.运行带有main方法的2.通过命令 Java -jar命令3 ...

  3. SpringBoot启动过程原理(转)

    1.1 Springboot启动: @SpringBootApplication public class ServerApplication { public static void main(St ...

  4. ASP.Net Core MVC6 RC2 启动过程分析[偏源码分析]

    入口程序 如果做过Web之外开发的人,应该记得这个是标准的Console或者Winform的入口.为什么会这样呢? .NET Web Development and Tools Blog ASP.NE ...

  5. 开机SystemServer到ActivityManagerService启动过程分析

    开机SystemServer到ActivityManagerService启动过程 一 从Systemserver到AMS zygote-> systemserver:java入层口: /** ...

  6. Neutron分析(2)——neutron-server启动过程分析

    neutron-server启动过程分析 1. /etc/init.d/neutron-server DAEMON=/usr/bin/neutron-server DAEMON_ARGS=" ...

  7. linux视频学习7(ssh, linux启动过程分析,加解压缩,java网络编程)

    回顾数据库mysql的备份和恢复: show databases; user spdb1; show tables; 在mysql/bin目录下 执行备份: ./mysqldump -u root - ...

  8. Activity启动过程分析

    Android的四大组件中除了BroadCastReceiver以外,其他三种组件都必须在AndroidManifest中注册,对于BroadCastReceiver来说,它既可以在AndroidMa ...

  9. Springboot启动源码详解

    我们开发任何一个Spring Boot项目,都会用到如下的启动类 @SpringBootApplication public class Application { public static voi ...

随机推荐

  1. 5. Longest Palindromic Substring - Unsolved

    https://leetcode.com/problems/longest-palindromic-substring/#/description Given a string s, find the ...

  2. windows10安装ubuntu on windows10

    安装向导 https://msdn.microsoft.com/en-us/commandline/wsl/install_guide

  3. 代理设计模式 (静态代理设计模式)+ 动态代理(JDK和Cglib)

    一.代理设计模式 1.设计模式:前人总结一套解决特定问题的代码 2.代理设计模式优点: 2.1 保护真实对象 2.2 让真实对象职责更明确 2.3 扩展 3.代理设计模式 3.1 真实对象(老总) 3 ...

  4. 深度学习中 epoch,[batch size], iterations概念解释

    one epoch:所有的训练样本完成一次Forword运算以及一次BP运算 batch size:一次Forword运算以及BP运算中所需要的训练样本数目,其实深度学习每一次参数的更新所需要损失函数 ...

  5. 2018.06.26 NOIP模拟 号码(数位dp)

    题目背景 SOURCE:NOIP2015-GDZSJNZX(难) 题目描述 Mike 正在在忙碌地发着各种各样的的短信.旁边的同学 Tom 注意到,Mike 发出短信的接收方手机号码似乎都满足着特别的 ...

  6. 假期训练六(poj-1753,递归+hdu-2844,多重背包)

    题目一:传送门 题意:有一个4*4的棋盘,每次翻转一个棋子和它的上下左右的四个棋子,判断翻转多少次之后可以变为纯色的棋盘. 思路:总共有C(0,16)+C(1,16)+C(2,16)+……+C(16, ...

  7. java正则表达式笔记

    import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntax ...

  8. 通过sftp操作Linux服务器上的文件(java)

    本文为实现对linux服务器文件的操作.windows服务器不支持. 引入jar包:jsch-0.1.42.jar package com.csvreader.sftp; import java.io ...

  9. 安装BouncyCastle

    对于Windows而言 将bcprov-jdk16-146.jar 复制到C:\Program Files\Java\jre6\lib\ext和C:\jdk1.6.0\jre\lib\ext目录下: ...

  10. 笔记:认识 head 标签 待 更新中……

    文档的头部描述了文档的各种属性和信息,包括文档的标题等.绝大多数文档头部包含的数据都不会真正作为内容显示给读者. 下面这些标签可用在 head 部分: <head> <title&g ...