应用启动过程——BootstrapContext

1,182 阅读4分钟

从本章节开始,将一步步探究SpringApplication#run方法的秘密,在介绍细节前,我们先把run的整个流程大概罗列出来,以便有个全局的视角。随着接下来的学习,将把这个图的细节逐步丰富起来。本节将先介绍图中第1、2两步骤。

把run方法的代码也贴出来供参考:

/**
 * 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) {
   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);
      configureIgnoreBeanInfo(environment);
      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) {
      handleRunFailure(context, ex, listeners);
      throw new IllegalStateException(ex);
   }
   try {
      Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
      listeners.ready(context, timeTakenToReady);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, null);
      throw new IllegalStateException(ex);
   }
   return context;
}

1 DefaultBootstrapContext

DefaultBootstrapContext bootstrapContext = createBootstrapContext();

既然是创建了一个DefaultBootstrapContext,那核心就应该是要先了解DefaultBootstrapContext是什么了。代码跟到DefaultBootstrapContext的父接口BootstrapContext,看一下注释

/**
 * A simple bootstrap context that is available during startup and {@link Environment}
 * post-processing up to the point that the {@link ApplicationContext} is prepared.
 * <p>
 * Provides lazy access to singletons that may be expensive to create, or need to be
 * shared before the {@link ApplicationContext} is available.
 *
 * @author Phillip Webb
 * @since 2.4.0
 */
public interface BootstrapContext {

土味翻译:这个上下文很简单,可以在“启动时”和“Environment的后处理期间”获取到它,直到ApplicationContext准备好后它就不可用了。这个上下文的可以用于为创建成本高昂的单例,或需要在容器初始化之前使用的单例,提供延迟访问的机制。

1.1 DefaultBootstrapContext的可用时机

从源码的角度来看看是怎么体现这个可用时机的:“启动时”和“Environment的后处理期间”,直到ApplicationContext准备好之后。

在run方法中,bootstrapContext的第一处引用是在

listeners.starting(bootstrapContext, this.mainApplicationClass);

跟进方法,不难发现,bootstrapContext被用作了SpringApplicationRunListener的starting方法入参,这就是所谓的“启动时”。

void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
   doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
         (step) -> {
            if (mainApplicationClass != null) {
               step.tag("mainApplicationClass", mainApplicationClass.getName());
            }
         });
}

bootstrapContext的第二处引用是在

ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);

跟进方法,SpringApplication#prepareEnvironment -> SpringApplicationRunListeners#environmentPrepared,发现bootstrapContext被用作了SpringApplicationRunListener的environmentPrepared方法入参,这就是所谓的“Environment的后处理期间”。

void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
   doWithListeners("spring.boot.application.environment-prepared",
         (listener) -> listener.environmentPrepared(bootstrapContext, environment));
}

bootstrapContext的第三处引用是在

prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);

跟进方法,SpringApplication#prepareContext -> DefaultBootstrapContext#close,发现在ApplicationContext prepared之后,广播了bootstrapContext的关闭事件BootstrapContextClosedEvent,此时事件的监听者还是可以拿到bootstrapContext的,这也是最后一次使用它的机会,这就是所谓的“直到ApplicationContext准备好之后就不可用”

public void close(ConfigurableApplicationContext applicationContext) {
   this.events.multicastEvent(new BootstrapContextClosedEvent(this, applicationContext));
}

1.2 DefaultBootstrapContext的使用原理

1.2.1 从源码分析如何使用DefaultBootstrapContext

要了解DefaultBootstrapContext的用途,自然需要看下这个类有什么属性,方法。

  • 持有两个Map,instances和instanceSuppliers,其中instances是存储对象实例,instanceSuppliers存储对象实例的提供者(可以理解为实例的工厂),它们的key是class对象,因此是用于单例的存储
  • events是个事件广播器,前文讲到的关闭事件就是用它发布的
  • 一些close监听及事件相关的方法,用于发布关闭事件,回调监听者
  • 一些get方法,获取的是instances中存储的实例,结合代码可以看到instances中的实例又是由instanceSuppliers生产提供
  • 一些register方法,注册的是instanceSupplier,用于生产instance实例

通过读createBootstrapContext方法可以看出,可以通过注册BootstrapRegistryInitializer来初始化DefaultBootstrapContext

private DefaultBootstrapContext createBootstrapContext() {
   DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
   this.bootstrapRegistryInitializers.forEach((initializer) -> initializer.initialize(bootstrapContext));
   return bootstrapContext;
}

而且BootstrapRegistryInitializer的获取逻辑在SpringApplication构造方法中

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   this.resourceLoader = resourceLoader;
   Assert.notNull(primarySources, "PrimarySources must not be null");
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
   this.bootstrapRegistryInitializers = new ArrayList<>(
         getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   this.mainApplicationClass = deduceMainApplicationClass();
}

跟进getSpringFactoriesInstances方法,容易知道最终是通过SpringFactoriesLoader.loadFactoryNames从spring.factories加载的BootstrapRegistryInitializer

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
   ClassLoader classLoader = getClassLoader();
   // Use names and ensure unique to protect against duplicates
   Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
   List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
   AnnotationAwareOrderComparator.sort(instances);
   return instances;
}

根据前文已分析,初始化好DefaultBootstrapContext之后,就可以在SpringApplicationRunListener的starting及environmentPrepared方法中使用DefaultBootstrapContext,也可以注册ApplicationListener事件监听器来监听DefaultBootstrapContext的close事件,做最后一步处理。

1.2.2 使用示例

创建一个CreatingCostlyBean,作为单例bean使用

package com.letterh.demo.bootstrapcontext;

public class CreatingCostlyBean {

}

创建一个MyInstanceSupplier,用于创建CreatingCostlyBean

package com.letterh.demo.bootstrapcontext;

import org.springframework.boot.BootstrapContext;
import org.springframework.boot.BootstrapRegistry;

public class MyInstanceSupplier implements BootstrapRegistry.InstanceSupplier<CreatingCostlyBean> {
    @Override
    public CreatingCostlyBean get(BootstrapContext context) {
        return new CreatingCostlyBean();
    }
}

创建了BootstrapContextCloseListener,用于监听close事件

package com.letterh.demo.bootstrapcontext;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.BootstrapContext;
import org.springframework.boot.BootstrapContextClosedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;

public class BootstrapContextCloseListener implements ApplicationListener<BootstrapContextClosedEvent> {

    @Override
    public void onApplicationEvent(BootstrapContextClosedEvent event) {
        System.out.println("监听到BootstrapContextClosedEvent");

        BootstrapContext bootstrapContext = event.getBootstrapContext();
        ConfigurableApplicationContext applicationContext = event.getApplicationContext();
        CreatingCostlyBean costlyBean = bootstrapContext.get(CreatingCostlyBean.class);
        ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();

        System.out.println("将creatingCostlyBean注册到IOC容器中");
        beanFactory.registerSingleton("creatingCostlyBean", costlyBean);
    }
}

创建一个MyBootstrapRegistryInitializer,并配置在spring.factories文件中,用于注册MyInstanceSupplier及BootstrapContextCloseListener

org.springframework.boot.BootstrapRegistryInitializer=\
  com.letterh.demo.bootstrapcontext.MyBootstrapRegistryInitializer
package com.letterh.demo.bootstrapcontext;

import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.BootstrapRegistryInitializer;

public class MyBootstrapRegistryInitializer implements BootstrapRegistryInitializer{
    @Override
    public void initialize(BootstrapRegistry registry) {
        System.out.println("执行BootstrapRegistryInitializer#initialize");
        registry.register(CreatingCostlyBean.class, new MyInstanceSupplier());

        System.out.println("注册BootstrapContextCloseListener");
        registry.addCloseListener(new BootstrapContextCloseListener());
    }
}

以上代码可以在我的demo工程中直接找到并运行,运行结果

2 Headless配置

简单介绍一下背景,Headless模式是系统的一种配置模式,在该模式下,系统缺少了显示设备、键盘或鼠标。配置java.awt.headless=true就是告诉程序,将工作在这种模式下。

java.awt.headless配置逻辑比较简单,本文也不会过多涉及,因此也放到当前章节一并讲完。直接看方法

private void configureHeadlessProperty() {
   System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
         System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}

如果程序配置了SYSTEM_PROPERTY_JAVA_AWT_HEADLESS(=java.awt.headless),就使用配置值,否则使用默认值this.headless(=true)