从本章节开始,将一步步探究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)