组里的实习生妹妹讲了一次Springboot的启动,
讲着讲着就讲到Spring的bean的生命周期去了,
我心想坏了,
这妮子估计把Springboot和Spring的相关逻辑给混淆了,
这必须得给她治一治。
前言
本文会对Springboot启动流程进行详细分析。但是请注意,Springboot启动流程是Springboot的逻辑,请千万不要将Springboot启动流程相关逻辑与Spring的相关逻辑混在一起,比如把Spring的bean生命周期的逻辑混在Springboot启动流程中,那么整个体系就复杂且混乱了。
所以本文仅重点关注Springboot启动流程,涉及Spring的部分,会略作说明并跳过。
整体的一个结构图如下。
Springboot版本:2.4.1
正文
一. Springboot启动流程图及说明
如下是Springboot的一个启动流程图。
在SpringApplication完成初始化后,就会调用SpringApplication对象的run() 方法,该方法就是Springboot启动的入口,也对应着全流程图中的开始。下面给出SpringApplication对象的run() 方法说明,如下所示。
java
复制代码
public ConfigurableApplicationContext run(String... args) { // 创建StopWatch,用于统计Springboot启动的耗时 StopWatch stopWatch = new StopWatch(); // 开始计时 stopWatch.start(); DefaultBootstrapContext bootstrapContext = createBootstrapContext(); ConfigurableApplicationContext context = null; configureHeadlessProperty(); // 获取运行时监听器 SpringApplicationRunListeners listeners = getRunListeners(args); // 调用运行时监听器的starting()方法 // 该方法需要在Springboot一启动时就调用,用于特别早期的初始化 listeners.starting(bootstrapContext, this.mainApplicationClass); try { // 获取args参数对象 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 读取Springboot配置文件并创建Environment对象 // 这里创建的Environment对象实际为ConfigurableEnvironment ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); configureIgnoreBeanInfo(environment); // 打印Banner图标 Banner printedBanner = printBanner(environment); // 创建ApplicationContext应用行下文,即创建容器 context = createApplicationContext(); context.setApplicationStartup(this.applicationStartup); // 准备容器 prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); // 初始化容器 refreshContext(context); afterRefresh(context, applicationArguments); // 停止计时 stopWatch.stop(); if (this.logStartupInfo) { // 打印启动耗时等信息 new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } // 调用运行时监听器的started()方法 // 该方法需要在应用程序启动后,CommandLineRunners和ApplicationRunners被调用前执行 listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, listeners); throw new IllegalStateException(ex); } try { // 调用运行时监听器的running()方法 // 该方法需要在SpringApplication的run()方法执行完之前被调用 listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, null); throw new IllegalStateException(ex); } return context; }
二. SpringApplication的初始化
通常,Springboot应用程序的启动类定义如下。
java
复制代码
@SpringBootApplication public class LearnStartApplication { public static void main(String[] args) { SpringApplication.run(LearnStartApplication.class, args); } }
从SpringApplication的静态run() 方法一路跟进,会发现如下的实现。
java
复制代码
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { return run(new Class<?>[] { primarySource }, args); } public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }
也就是Springboot启动时会先创建SpringApplication,然后再通过SpringApplication的run() 方法完成启动。所以下面分析一下SpringApplication的初始化逻辑,其构造方法如下所示。
java
复制代码
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); // 设置源 // 通常Springboot的启动类就是源 this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); // 推断并设置WEB应用程序类型 // 根据classpath下的类来推断 this.webApplicationType = WebApplicationType.deduceFromClasspath(); // 加载并设置Bootstrapper this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class)); // 加载并设置初始化器 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); // 加载并设置应用事件监听器 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); // 推断并设置应用程序主类的Class对象 this.mainApplicationClass = deduceMainApplicationClass(); }
梳理一下在SpringApplication的构造方法中,做了如下事情。
- 设置源。通常,Springboot中的源就是Springboot的启动类;
- 设置WEB应用程序类型。通过判断classpath下是否存在某些类,来推断当前WEB应用程序的类型;
- 加载并设置Bootstrapper,ApplicationContextInitializer和ApplicationListener。借助SpringFactoriesLoader基于SPI机制完成Bootstrapper,ApplicationContextInitializer和ApplicationListener的加载,然后设置到SpringApplication中;
- 设置应用程序主类的Class对象。
下面对上述事情进行分析。
1. 设置源
这里的源,也就是Spring容器启动时依赖的初始配置类,在Springboot中,初始配置类通常为启动类。下面可以通过调试看一下primarySources字段的值,如下所示。
可见源就是Springboot的启动类的Class对象。
2. 设置WEB应用程序类型
WebApplicationType#deduceFromClasspath方法如下所示。
java
复制代码
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" }; private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet"; privat