springboot框架启动流程一(启动流程分析)

213 阅读5分钟

1.引言

上篇文章我们知道了,springboot应用启动流程分为两步:

  • 通过@SpringBootApplication注解完成组件扫描,以及自动装配
  • 通过SpringApplication.run方法完成引导应用程序启动

那么本篇文章,我们一起来探讨run方法是如何完成应用程序的引导启动的?引导启动的步骤稍微有点多和繁琐,但如果你熟悉spring应用启动流程的话,还是很容易就理解了,springboot应用的启动流程,是在spring应用启动流程的基础上,做了一些扩展,仅此而已。

具体来看看。

2.启动流程分析

2.1.应用入口

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

2.2.进入run方法

/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified source using default settings.
* @param primarySource the primary source to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
	return run(new Class<?>[] { primarySource }, args);
}

2.3.进入重载run方法:

/**
 * Static helper that can be used to run a {@link SpringApplication} from the
* specified sources using default settings and user supplied arguments.
* @param primarySources the primary sources to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        // 创建SpringApplication实例,并调用run方法
		return new SpringApplication(primarySources).run(args);
}

重点关注的run方法:

  • 方法注释写的很清楚,创建和刷新ApplicationContext对象,我们知道ApplicationContext即是spring应用的核心
  • 返回ConfigurableApplicationContext实例,即IoC容器
  • 方法内容有点多,我们通过标记序号,然后依据序号在后面详细分析每个步骤
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	ConfigurableApplicationContext context = null;
	configureHeadlessProperty();
    // 第一步
	SpringApplicationRunListeners listeners = getRunListeners(args);
	listeners.starting();
	try {
    // 第二步
	ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
	ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
	configureIgnoreBeanInfo(environment);
    // 第三步
	Banner printedBanner = printBanner(environment);
    // 第四步
	context = createApplicationContext();
	// 第五步
    prepareContext(context, environment, listeners, applicationArguments, printedBanner);
	// 第六步
    refreshContext(context);
    // 第七步
	afterRefresh(context, applicationArguments);
	
    stopWatch.stop();
	if (this.logStartupInfo) {
	 new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
	}
	listeners.started(context);
	callRunners(context, applicationArguments);
  }catch (Throwable ex) {
		handleRunFailure(context, ex, listeners);
		throw new IllegalStateException(ex);
  }

	try {
		listeners.running(context);
	}catch (Throwable ex) {
		handleRunFailure(context, ex, null);
		throw new IllegalStateException(ex);
	}
    
	return context;
}

第一步: 首先是关于事件监听的处理,springboot在启动过程中,通过监听器机制给应用程序预留了扩展能力。这里的

SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();

获取到的是事件发布者。在监听器机制中,共有三个角色:

  • 事件
  • 事件监听器
  • 事件发布者

那么SpringApplicationRunListener扮演的是事件发布者,它会在springboot应用启动的不同时间点,发布不同类型的事件(ApplicationEvent)。然后相应的事件监听器便会接受到相应的事件,并进行处理。

SpringApplicationRunListener源码:每个方法相应在不同的时间点进行事件发布

public interface SpringApplicationRunListener {
    // 调用run方法后,最先调用的方法,用于完成早期的初始化工作
	default void starting() {}

	// 运行环境:environment准备好,且在ApplicationContext创建前调用
	default void environmentPrepared(ConfigurableEnvironment environment) {}

	// 创建完成ApplicationContext后调用
	default void contextPrepared(ConfigurableApplicationContext context) {}

	// ApplicationContext加载完成,在refresh前调用
	default void contextLoaded(ConfigurableApplicationContext context) {}

	// ApplicationContext完成refresh,且应用启动后调用
	default void started(ConfigurableApplicationContext context) {}

	// SpringApplication的run调用完成,且ApplicationContext完成refresh后调用
	default void running(ConfigurableApplicationContext context) {}

	// 启动过程中,发生意外的时候调用 
	default void failed(ConfigurableApplicationContext context, Throwable exception) {}

}

SpringApplicationRunListener接口,唯一的实现类:

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
    @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) {
		this.initialMulticaster
				.multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
	}
    .............................................................
}

源码有点多,就不贴完整的了。从starting,environmentPrepared,contextPrepared分别发布了不同的事件:

  • ApplicationStartingEvent
  • ApplicationEnvironmentPreparedEvent
  • ApplicationContextInitializedEvent

到这里,我们基本上搞清楚了第一步做的事情。

第二步:

第二步主要是准备运行环境:Environment,包含两个方面的内容

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
  • 分环境配置特性:profile
  • 属性配置:properties

有一定开发经验的同学,对这两块内容都不会陌生。profile特性用于指定不同的应用运行环境,比如说:开发环境,测试环境,预发布环境,生产环境,可以使用不同的配置文件;properties属性来源于配置文件,环境变量,命令行参数等。

当Environment准备好之后,应用生命周期的任何时候,都可以从Environment中获取资源配置。

第三步:

第三步不需要过多的介绍,我截个图你就知道是什么了

Banner printedBanner = printBanner(environment);

image.png

第四步:

根据是否是web应用,来创建对应的ApplicationContext

context = createApplicationContext();

第五步:

初始化ApplicationContext,主要完成以下工作:

  • 将准备好的Environment设置给ApplicationContext
  • 遍历调用ApplicationContextInitializer的initialize()方法,来对已经创建好的ApplicationContext进行进一步的处理
  • 调用SpringApplicationRunListener的contextPrepared()方法,通知所有的监听者:ApplicationContext已经准备完毕
  • 将所有的bean加载到容器中
  • 调用SpringApplicationRunListener的contextLoaded()方法,通知所有的监听者:ApplicationContext已经装载完毕
 prepareContext(context, environment, listeners, applicationArguments, printedBanner);

第六步:

调用ApplicationContext的refresh方法,完成IoC容器可用的最后一道工序 。

 refreshContext(context);
refresh的意思是刷新,简单解释一下刷新到底做了什么?为什么刚创建,初始化的ApplicationContext需要刷新呢?新鲜的还要刷新,显得有那么点突然和怪异!

其实它要做什么事情呢?就是获取BeanFactoryPostProcessor来对容器做一些额外的操作。BeanFactoryPostProcessor允许我们在容器实例化相应对象之前,对注册到容器的BeanDefinition所保存的信息做一些额外的操作。

第七步:

查找当前context中是否注册有CommandLineRunner和ApplicationRunner,如果有则遍历执行它们 。大多数应用,我们不太需要关注这一步。

afterRefresh(context, applicationArguments);

到此,基本上就是springboot应用整个的启动引导流程了。

最后总结一下需要关注的几个步骤:

  • 第一步:与事件监听机制相关,需要重点关注
  • 第二步:与应用的运行环境相关,需要重点关注
  • 第四步:创建应用上下文ApplicationContext,需要重点关注
  • 第五步:初始化准备ApplicationContext容器,需要重点关注
  • 第六步:完成IoC容器可用的最后一道工序,需要重点关注