Spring Boot 深入剖析:SpringBoot的启动流程
本章我们来讲一下整个SpringBoot的启动流程,在程序启动的时候其实是运行的main方法当做程序的入口来实现的。而main方法则是调用的run方法来实现的。因此整个SpringBoot的启动流程的逻辑都在run方法中的。
run方法的源码分析
public ConfigurableApplicationContext run(String... args) {long startTime = System.nanoTime();DefaultBootstrapContext bootstrapContext = createBootstrapContext();ConfigurableApplicationContext context = null;configureHeadlessProperty();SpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting(bootstrapContext, this.mainApplicationClass);try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);Banner printedBanner = printBanner(environment);context = createApplicationContext();context.setApplicationStartup(this.applicationStartup);prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);refreshContext(context);afterRefresh(context, applicationArguments);Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);}listeners.started(context, timeTakenToStartup);callRunners(context, applicationArguments);}catch (Throwable ex) {if (ex instanceof AbandonedRunException) {throw ex;}handleRunFailure(context, ex, listeners);throw new IllegalStateException(ex);}try {if (context.isRunning()) {Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);listeners.ready(context, timeTakenToReady);}}catch (Throwable ex) {if (ex instanceof AbandonedRunException) {throw ex;}handleRunFailure(context, ex, null);throw new IllegalStateException(ex);}return context;}
上述代码则是run方法的主要核心代码,下面我们来对其进行详细分析。
DefaultBootstrapContext bootstrapContext = createBootstrapContext();private DefaultBootstrapContext createBootstrapContext() {DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();this.bootstrapRegistryInitializers.forEach((initializer) -> initializer.initialize(bootstrapContext));return bootstrapContext;
}
首先则是调用了createBootstrapContext这个方法,而createBootstrapContext这个方法的源码我们可以看到当前是new了一个DefaultBootstrapContext 类(这个类之前已经讲过本质上是springBoot启动过程中的辅助工具类),随后是遍历当前SpringApplication所含的bootstrapRegistryInitializers集合,一个一个调用bootstrapRegistryInitializer来对其DefaultBootstrapContext对象进行初始化操作。
configureHeadlessProperty();private void configureHeadlessProperty() {System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}
随后则是调用了configureHeadlessProperty方法,该方法的作用则是设置一个系统属性。由源码可以看到当前,设置了一个SYSTEM_PROPERTY_JAVA_AWT_HEADLESS的属性,其中设置getProperty则是查看是否预先已经有当前属性值,如果有则用当前有的,没有则使用getProperty第三个参数值(用作默认值)
SpringApplicationRunListeners listeners = getRunListeners(args);private SpringApplicationRunListeners getRunListeners(String[] args) {ArgumentResolver argumentResolver = ArgumentResolver.of(SpringApplication.class, this);argumentResolver = argumentResolver.and(String[].class, args);List<SpringApplicationRunListener> listeners = getSpringFactoriesInstances(SpringApplicationRunListener.class,argumentResolver);SpringApplicationHook hook = applicationHook.get();SpringApplicationRunListener hookListener = (hook != null) ? hook.getRunListener(this) : null;if (hookListener != null) {listeners = new ArrayList<>(listeners);listeners.add(hookListener);}return new SpringApplicationRunListeners(logger, listeners, this.applicationStartup);}
第三个方法则是调用了getRunListeners方法目的是获取并初始化SpringApplicationRunListeners类,下面我们来看看源码解析。在源码中我们可以看到首先调用getSpringFactoriesInstances方法来实例化得到一个SpringApplicationRunListener的集合。随后处理 Spring Boot 的应用钩子机制:
applicationHook
是一个ThreadLocal<SpringApplicationHook>
,用于支持测试或其他特殊场景如果存在钩子,通过
hook.getRunListener(this)
获取一个额外的SpringApplicationRunListener
将这个额外的监听器添加到监听器列表中
applicationHook
是 Spring Boot 提供的一个基于ThreadLocal
的钩子机制,它允许在运行时动态地向 Spring Boot 应用添加额外的功能,包括额外的SpringApplicationRunListener
。
是用于添加用户自定义的监听器(通过继承实现方法来实现)
主要面向框架开发者和高级用户
提供了运行时动态扩展的能力
特别适用于测试框架和其他需要环境特定行为的场景
与传统的
spring.factories
机制互补,提供了更大的灵活性
方式 使用场景 优势 劣势 spring.factories
常规扩展,所有环境都需要的监听器 声明式,自动发现 需要修改配置文件 applicationHook
运行时动态扩展,环境特定的监听器 编程式,灵活控制 需要手动设置
最后则是new一个SpringApplicationRunListeners,并且将得到的SpringApplicationRunListener当做构造方法的参数传递进去实现初始化的操作。
listeners.starting(bootstrapContext, this.mainApplicationClass);
随后则是使用listeners来发布了一个事件对应的事件监听器收到后则会做出处理。
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {// Create and configure the environmentConfigurableEnvironment environment = getOrCreateEnvironment();configureEnvironment(environment, applicationArguments.getSourceArgs());ConfigurationPropertySources.attach(environment);listeners.environmentPrepared(bootstrapContext, environment);DefaultPropertiesPropertySource.moveToEnd(environment);Assert.state(!environment.containsProperty("spring.main.environment-prefix"),"Environment prefix cannot be set via properties.");bindToSpringApplication(environment);if (!this.isCustomEnvironment) {EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());}ConfigurationPropertySources.attach(environment);return environment;
}
之后则是将args封装为一个applicationArguments 对象,随后调用了prepareEnvironment用来进行配置信息的加载。对于prepareEnvironment方法的源码,首先是直接初始化了一个ConfigurableEnvironment对象,随后则是将main函数的args封装的对象设置到environment对象中去,随后attach方法则是将 Spring Boot 的高级配置属性处理机制附加到environment中。然后则是通过SpringApplicationRunListeners 来发布事件机制,随后则是将environment与当前的SpringApplication进行绑定。之后呢则是查看当前environment环境用户是否自定义了如果有则进行切换。最后再次调用attach方法然后返回environment。
两次调用
ConfigurationPropertySources.attach()
不是重复劳动,而是精心设计的:
第一次调用:为配置文件加载阶段提供配置属性支持
第二次调用:为应用运行阶段提供配置属性支持,并处理环境可能被替换的情况
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
这个printBanner是 Spring Boot 启动过程中负责处理和控制应用启动横幅(Banner)显示的核心语句。也就是启动时候打印Spring的一个横幅文字。然后则创建了ApplicationContext这个对象,随后使用了setApplicationStartup方法,它负责将应用启动指标收集功能传递给 Spring 应用上下文。
ApplicationStartup
是 Spring Framework 5.3 引入的接口,用于收集应用启动过程中的性能指标数据。它提供了一种标准化的方式来记录启动过程中各个步骤的耗时和详细信息。
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments, Banner printedBanner) {context.setEnvironment(environment);postProcessApplicationContext(context);addAotGeneratedInitializerIfNecessary(this.initializers);applyInitializers(context);listeners.contextPrepared(context);bootstrapContext.close(context);if (this.logStartupInfo) {logStartupInfo(context.getParent() == null);logStartupProfileInfo(context);}// Add boot specific singleton beansConfigurableListableBeanFactory beanFactory = context.getBeanFactory();beanFactory.registerSingleton("springApplicationArguments", applicationArguments);if (printedBanner != null) {beanFactory.registerSingleton("springBootBanner", printedBanner);}if (beanFactory instanceof AbstractAutowireCapableBeanFactory autowireCapableBeanFactory) {autowireCapableBeanFactory.setAllowCircularReferences(this.allowCircularReferences);if (beanFactory instanceof DefaultListableBeanFactory listableBeanFactory) {listableBeanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);}}if (this.lazyInitialization) {context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());}context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));if (!AotDetector.useGeneratedArtifacts()) {// Load the sourcesSet<Object> sources = getAllSources();Assert.notEmpty(sources, "Sources must not be empty");load(context, sources.toArray(new Object[0]));}listeners.contextLoaded(context);}
随后的三个方法则是初始化ApplicationContext,以及正式启动ApplicationContext和启动ApplicationContext之后执行的业务方法。其中正式启动ApplicationContext方法本章不会讲解,后续专门出一章来进行讲解。首先我们看看初始化ApplicationContext的方法源码,第一步则是将environment对象放到ApplicationContext当中,随后的这个postProcessApplicationContext方法的作用则是在应用上下文创建后但尚未刷新之前,对其进行额外的配置和定制化处理。同时这个方法也是一个模版方法可以交给子类去重写方法。随后则是调用了ApplicationContextInitializer的initialize方法来实现对ApplicationContext的初始化配置,当初始化完成之后则采用listener发布当前事件,发布完成之后则关闭当前bootstrapContext。
随后则是注册关键组件:将启动参数和 Banner 注册为 Bean(加载特定的单例bean),配置容器行为:设置循环引用和 Bean 定义覆盖策略,启用高级特性:支持延迟初始化和属性源排序(加载),加载用户配置:处理主配置类和组件扫描,发布事件通知:告知监听器上下文已加载完成(listeners发布事件)。
而后续的afterRefresh则是一个模版方法用来留给子类去执行,当容器刷新完毕之后则执行当前的方法
listeners.started(context, timeTakenToStartup);this.callRunners(context, applicationArguments);
最后则是通过SpringApplicationRunListeners来实现发布事件并且callRunners方法的主要作用是查找并执行所有实现了 ApplicationRunner
和 CommandLineRunner
接口的 Bean,为开发者提供了一个在应用完全启动后执行自定义初始化代码的标准方式。