写点什么

源码 | SpringBoot 启动流程大揭秘

  • 2022 年 9 月 07 日
    北京
  • 本文字数:4385 字

    阅读完需:约 14 分钟

源码 | SpringBoot启动流程大揭秘

什么是 SpringBoot

日常开发中采用的是开源的若依框架,也就是 SpringBoot 框架,那么什么是 SpringBoot 框架呢?

SpringBoot 是一款开箱即用框架,提供各种默认配置来简化项目配置,让我们的 Spring 应用变的更轻量化、更快的入门,在主程序执行 main 函数就可以运行,也可以打包你的应用为 jar 并通过使用 java -jar 来运行你的 Web 应用。 使用 SpringBoot 只需很少的配置,大部分的时候直接使用默认的配置即可。同时后续也可以与 Spring Cloud 的微服务无缝结合。

SpringBoot 启动流程

SpringBoot 启动流程涉及到的步骤相对来说容易理解,这里我先准备一个启动类

启动类需要标注 @SpringBootApplication 的注解,然后就可以直接以 main 函数的方式执行 SpringApplication.run(DemoApplication.class, args);就可以启动项目,非常简单,下面我们再逐步分析每一步执行流程,main 函数代码

@SpringBootApplicationpublic class DemoApplication{    public static void main(String[] args)    {        SpringApplication.run(DemoApplication.class, args);    }}
复制代码

SpringApplication.run

首先我们跟进 SpringApplication.run(DemoApplication.class, args);方法可以看到

run 方法是一个 static 方法,继续点击 run(new Class[] { primarySource }, args);方法可以看到调用了另一个 run 方法

SpringApplication 初始化

在第二个 static 静态 run 方法中可以看到 new 了一个 SpringApplication 对象,同时继续调用其的 run 方法来启动 SpringBoot 项目,下面我们先来看一下 SpringApplication 对象是如何构建的,进入 new SpringApplication(primarySources)的构造方法可以看到

其中 primarySources 就是启动类的 class 对象,继续跟进可以看到 SpringApplication 构造类加载信息

WebApplicationType

在 SpringApplication 的构造类中通过 WebApplicationType.deduceFromClasspath();判断当前应用程序的容器,一共有三种容器,更进去可以看到 WebApplicationType 如图

可以看到包含三种容器 REACTIVE、NONE、SERVLET,默认用的是 WebApplicationType.SERVLET 容器。

加载 spring.factories

再回到 SpringApplication 对象继续往下看,可以看到 this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();方法 getBootstrapRegistryInitializersFromSpringFactories();从 spring.factories 中获取 BootstrapRegistryInitializer,源码

加入源码解析

@SuppressWarnings("deprecation")private List<BootstrapRegistryInitializer> getBootstrapRegistryInitializersFromSpringFactories() {    ArrayList<BootstrapRegistryInitializer> initializers = new ArrayList<>();    //从spring.factories中获取Bootstrapper集合,遍历转化为BootstrapRegistryInitializer并存入initializers    getSpringFactoriesInstances(Bootstrapper.class).stream()            .map((bootstrapper) -> ((BootstrapRegistryInitializer) bootstrapper::initialize))            .forEach(initializers::add);    //从spring.factories中获取BootstrapRegistryInitializer集合并存入initializers,最后返回initializers集合    initializers.addAll(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));    return initializers;}
复制代码

继续回到 SpringApplication 对象继续往下看会看到

其中 getSpringFactoriesInstances(ApplicationContextInitializer.class)和 getSpringFactoriesInstances(ApplicationListener.class)分别表示从 spring.factories 中获取容器上下文相关初始化 ApplicationContextInitializer 和容器监听器相关初始化 ApplicationListener

获取完容器上下文初始化和监听器初始化器之后通过 setInitializers((Collection)和 setListeners((Collection)分别放入 List<ApplicationContextInitializer> initializers 和 List<ApplicationListener> listeners,这里我们来启动一下程序看一下是否是这样


可以看到已经将 spring.factories 中的配置加载进来了。

deduceMainApplicationClass

后面继续跟进可以看到 deduceMainApplicationClass()理解为推断推论主程序类,debug 可以看到获取了 main 函数所在的主程序类

自定义 spring.factories

增加一个类 ApplicationInit 实现上下文初始化接口 ApplicationContextInitializer,重写 initialize 方法,

public class ApplicationInit implements ApplicationContextInitializer {    @Override    public void initialize(ConfigurableApplicationContext applicationContext) {        System.out.println("加载自定义ApplicationContextInitializer...");    }}
复制代码

同时增加项目 spring.factories 配置

# Application Context Initializersorg.springframework.context.ApplicationContextInitializer=\com.ruoyi.web.controller.common.ApplicationInit
复制代码


启动应用程序可以看到

初始化完成 SpringApplication 之后就可以运行 run 方法了

SpringBoot 启动 run

初始化完成之后就可以正式进入 run 阶段了

stopWatch.start()

run 方法开始先初始化一个计时器,开启计时,之后会初始化一个上下文对象

createBootstrapContext

初始化上下文对象,这里将初始化 SpringApplication 是从 spring.factories 中获取的 bootstrapRegistryInitializers 进行初始化

之后继续向下看到 configureHeadlessProperty();该方法主要设置系统 headless 属性默认值是 true,查看源码

getRunListeners

从 spring.factories 中获取运行监听器 EventPublishingRunListener

debug 该方法可以看到从配置中加载的运行监听器方法

后续继续启动监听器 listeners.starting(),调用 starting()方法

prepareEnvironment

我们继续看 prepareEnvironment 方法,跟进去可以看到当前方法涉及 getOrCreateEnvironment、configureEnvironment、ConfigurationPropertySources、DefaultPropertiesPropertySource、bindToSpringApplication、convertEnvironment,

这里我们来 debug 看一下执行过程中环境加载情况

加载完成之后执行 configureIgnoreBeanInfo 方法配置忽略的 bean 信息,继续往下看

printBanner

打印配置的 banner 文本信息

banner 文件路径在\src\main\resources\banner.txt,可以通过更该文件内容展示不同的启动成功信息。

继续向下看,看到 createApplicationContext 方法创建应用程序的上下文容器,创建完之后,继续看 prepareContext,该方法主要配置容器的基本信息

prepareContext

prepareContext 也是一个重要的方法,我们来看一下源码方法

prepareContext 主要是为容器上下文设置容器环境变量、postProcessApplicationContext(context)设置容器的 ResourceLoader、ClassLoader、ConversionService,listeners.contextPrepared(context)触发所有 SpringApplicationRunListener 监听器的 contextPrepared 事件方法,getAllSources()Load the sources 加载所有的资源以及 load(context, sources.toArray(new Object[0]))Load beans into the application context 加载启动类 the context to load beans into 将启动类注入容器。

refreshContext

配置完容器基本信息后,刷新容器上下文 refreshContext 方法,

继续跟进去可以看到

这里我们看 web 容器的类,

源码如下,这里不再详细介绍里面的每一步加载了,先写主流程

	@Override	public void refresh() throws BeansException, IllegalStateException {		synchronized (this.startupShutdownMonitor) {			StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// Prepare this context for refreshing. prepareRefresh();
// Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory);
try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process"); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); beanPostProcess.end();
// Initialize message source for this context. initMessageSource();
// Initialize event multicaster for this context. initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses. onRefresh();
// Check for listener beans and register them. registerListeners();
// Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event. finishRefresh(); }
catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); }
// Destroy already created singletons to avoid dangling resources. destroyBeans();
// Reset 'active' flag. cancelRefresh(ex);
// Propagate exception to caller. throw ex; }
finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); contextRefresh.end(); } } }
复制代码

刷新容器上下文 refreshContext 方法之后看到 afterRefresh 是一个空方法,主要用于开发者拓展使用

listeners.started(context)

容器配置都完成之后,这时监听应用上下文启动完成所有的运行监听器调用 started() 方法,发布监听应用的启动事件,如图

后续继续执行 callRunners 方法遍历所有 runner,调用 run 方法

上述都完成之后到了最后一步,执行 listener.running 方法

运行所有运行监听器,该方法执行以后 SpringApplication.run(DemoApplication.class, args)也就算执行完成了,那么 SpringBoot 的 ApplicationContext 也就启动完成了。

总结

SpringBoot 的执行流程整体上分为两个部分,也就是 SpringApplication 的初始化和 SpringApplication.run 方法,所有的启动加载过程都在这两个方法中,一篇文章写的太多不方便阅读,另外个人觉得太长的文章也没有人有耐心看完,所以中间一些细节没有细究,后面会继续补充里面细节的内容,感谢大家的阅读,欢迎有问题的评论区留言,共同学习共同成长。

发布于: 刚刚阅读数: 3
用户头像

让技术不再枯燥,让每一位技术人爱上技术 2022.07.22 加入

还未添加个人简介

评论

发布
暂无评论
源码 | SpringBoot启动流程大揭秘_源码_六月的雨在infoQ_InfoQ写作社区