- The Bootstrap Application Context概念
这里是引用
A Spring Cloud application operates by creating a “bootstrap” context, which is a parent context for the main application. It is responsible for loading configuration properties from the external sources and for decrypting properties in the local external configuration files. The two contexts share an Environment, which is the source of external properties for any Spring application. By default, bootstrap properties (not bootstrap.properties but properties that are loaded during the bootstrap phase) are added with high precedence, so they cannot be overridden by local configuration.
翻译下意思:
Spring云应用程序bootstrap context进行操作(bootstrap context是主应用程序的父上下文)。bootstrap context负责从外部源加载配置属性,并解密本地外部配置文件中的属性。这两个上下文共享一个环境,该环境是任何Spring应用程序外部属性的来源。默认情况下,bootstrap属性(不是bootstrap.properties而是引导阶段加载的属性)具有较高的优先级,因此不能被本地配置覆盖。
截个图展示一下主上下文和bootstrap上下文
有了上图的直接认识后,我们来一步一步梳理下是怎么生成bootstrap context的
2. 入口代码
@EnableAutoConfiguration
public class SpringBootApplicationBootstrap {
public static void main(String[] args) {
new SpringApplicationBuilder(SpringBootApplicationBootstrap.class)
.run(args);
}
}
SpringApplicationBuilder run方法org.springframework.boot.builder.SpringApplicationBuilder#run 进去
public ConfigurableApplicationContext run(String... args) {
if (this.running.get()) {
// If already created we just return the existing context
return this.context;
}
configureAsChildIfNecessary(args);
if (this.running.compareAndSet(false, true)) {
synchronized (this.running) {
// If not already running copy the sources over and then run.
// 这里进去就是SpringApplication 的run方法
this.context = build().run(args);
}
}
return this.context;
}
org.springframework.boot.SpringApplication#run(java.lang.String…) 方法进去, 这个就springboot启动的主方法,会将几个关键路径梳理
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//1. 准备环境, 同时会进行广播事件, 广播到org.springframework.cloud.bootstrap.BootstrapApplicationListener 监听,
// BootstrapApplicationListener监听器很重要, 监听器中会启动bootstrapcontext
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 2. 准备上下文时会,会设置主上下的parent为bootstrap 上下文
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
3.org.springframework.boot.SpringApplication#prepareEnvironment
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 1. 广播事件,最终会调到org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
如下图,广播事件给监听器, 其中标出的就是org.springframework.cloud.bootstrap.BootstrapApplicationListener
org.springframework.cloud.bootstrap.BootstrapApplicationListener 是在spring.factories里配置的
在构造SpringApplication的时候通过spi的方式加载的
4. org.springframework.cloud.bootstrap.BootstrapApplicationListener 的监听方法
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
true)) {
return;
}
// don't listen to events in a bootstrap context
if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
return;
}
ConfigurableApplicationContext context = null;
String configName = environment
.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
for (ApplicationContextInitializer<?> initializer : event.getSpringApplication()
.getInitializers()) {
if (initializer instanceof ParentContextApplicationContextInitializer) {
context = findBootstrapContext(
(ParentContextApplicationContextInitializer) initializer,
configName);
}
}
if (context == null) {
// 启动bootstrap上下文,传参event.getSpringApplication()就是主程序上下文
context = bootstrapServiceContext(environment, event.getSpringApplication(),
configName);
}
apply(context, event.getSpringApplication(), environment);
}
org.springframework.cloud.bootstrap.BootstrapApplicationListener#bootstrapServiceContext 启动bootstrap上下文
private ConfigurableApplicationContext bootstrapServiceContext(
ConfigurableEnvironment environment, final SpringApplication application,
String configName) {
StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
MutablePropertySources bootstrapProperties = bootstrapEnvironment
.getPropertySources();
for (PropertySource<?> source : bootstrapProperties) {
bootstrapProperties.remove(source.getName());
}
String configLocation = environment
.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
Map<String, Object> bootstrapMap = new HashMap<>();
bootstrapMap.put("spring.config.name", configName);
// if an app (or test) uses spring.main.web-application-type=reactive, bootstrap will fail
// force the environment to use none, because if though it is set below in the builder
// the environment overrides it
bootstrapMap.put("spring.main.web-application-type", "none");
if (StringUtils.hasText(configLocation)) {
bootstrapMap.put("spring.config.location", configLocation);
}
bootstrapProperties.addFirst(
new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
for (PropertySource<?> source : environment.getPropertySources()) {
if (source instanceof StubPropertySource) {
continue;
}
bootstrapProperties.addLast(source);
}
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
List<String> names = new ArrayList<>(SpringFactoriesLoader
.loadFactoryNames(BootstrapConfiguration.class, classLoader));
for (String name : StringUtils.commaDelimitedListToStringArray(
environment.getProperty("spring.cloud.bootstrap.sources", ""))) {
names.add(name);
}
// TODO: is it possible or sensible to share a ResourceLoader?
// 创建
SpringApplicationBuilder builder = new SpringApplicationBuilder()
.profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
.environment(bootstrapEnvironment)
// Don't use the default properties in this builder
.registerShutdownHook(false).logStartupInfo(false)
.web(WebApplicationType.NONE);
final SpringApplication builderApplication = builder.application();
if(builderApplication.getMainApplicationClass() == null){
// gh_425:
// SpringApplication cannot deduce the MainApplicationClass here
// if it is booted from SpringBootServletInitializer due to the
// absense of the "main" method in stackTraces.
// But luckily this method's second parameter "application" here
// carries the real MainApplicationClass which has been explicitly
// set by SpringBootServletInitializer itself already.
builder.main(application.getMainApplicationClass());
}
if (environment.getPropertySources().contains("refreshArgs")) {
// If we are doing a context refresh, really we only want to refresh the
// Environment, and there are some toxic listeners (like the
// LoggingApplicationListener) that affect global static state, so we need a
// way to switch those off.
builderApplication
.setListeners(filterListeners(builderApplication.getListeners()));
}
List<Class<?>> sources = new ArrayList<>();
for (String name : names) {
Class<?> cls = ClassUtils.resolveClassName(name, null);
try {
cls.getDeclaredAnnotations();
}
catch (Exception e) {
continue;
}
sources.add(cls);
}
AnnotationAwareOrderComparator.sort(sources);
builder.sources(sources.toArray(new Class[sources.size()]));
// bootstrap上下文SpringApplicationBuilder 的run方法
final ConfigurableApplicationContext context = builder.run();
// gh-214 using spring.application.name=bootstrap to set the context id via
// `ContextIdApplicationContextInitializer` prevents apps from getting the actual
// spring.application.name
// during the bootstrap phase.
context.setId("bootstrap");
// Make the bootstrap context a parent of the app context
// 创建主程序application的初始化器,如下图; 会将bootstrap context放到初始化器的parent中,初始化器再将parent传给主程序上下文
addAncestorInitializer(application, context);
// It only has properties in it now that we don't want in the parent so remove
// it (and it will be added back later)
bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
return context;
}
主application新增的初始化器
- 初始化器再将parent传给主程序上下文,org.springframework.boot.SpringApplication#prepareContext方法内调用初始化器
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
// 应用初始化器
applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
org.springframework.cloud.bootstrap.BootstrapApplicationListener.AncestorInitializer#initialize 调用BootstrapApplicationListener.AncestorInitializer的initialize方法进行初始化
public void initialize(ConfigurableApplicationContext context) {
while (context.getParent() != null && context.getParent() != context) {
context = (ConfigurableApplicationContext) context.getParent();
}
reorderSources(context.getEnvironment());
// 实际是调用的ParentContextApplicationContextInitializer类的初始化方法, 这里parent就是bootstrap, context就是主程序应用上下文
new ParentContextApplicationContextInitializer(this.parent)
.initialize(context);
}
org.springframework.boot.builder.ParentContextApplicationContextInitializer的初始化方法
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
if (applicationContext != this.parent) {
// 设置parent
applicationContext.setParent(this.parent);
applicationContext.addApplicationListener(EventPublisher.INSTANCE);
}
}
总结:
- 构建SpringApplication时会将BootstrapApplicationListener监听器通过spi方式加载并注册
- 主SpringApplication run时,会prepareEnvironment, 并广播 ApplicationEnvironmentPreparedEvent事件
- BootstrapApplicationListener 监听到事件,进行bootstrapServiceContext启动bootstrap上下文, bootstrapServiceContext后addAncestorInitializer 添加初始化器给主应用上下文
- 主SpringApplication run方法内,调用prepareContext准备上下文方法时会调用applyInitializers应用初始化器
- 第3步添加的初始化器,org.springframework.cloud.bootstrap.BootstrapApplicationListener.AncestorInitializer(bootstrap context存在AncestorInitializer的parent中)会将bootstrap context设为main context的parent