Nacos源码分析四、BootstrapApplicationListener运行原理(1)
要理解nacos的配置变更如何更新到应用的配置上,这部分的内容属于SpringCloud部分的,要全部串起来,需要了解spring-cloud-context和spring-cloud-alibaba-nacos-config相关的内容。以下学习过程spring-cloud-alibaba-nacos-config版本为2.2.1.RELEASE,对应spring-cloud-context版本为2.2.2.RELEASE。涉及到spring的内容使用版本为 5.2.4.RELEASE
其实写着一部分的时候挺纠结的,实际上这部分的内容应该属于Spring的范畴,只是由于nacos引出来的,就暂时放这个地方吧。
我们先看BootStrapApplicationListener怎么来的吧
首先springboot启动,创建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();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));这一句添加ApplicationListener监听器。当我们引入spring-cloud-alibaba-nacos-config时,会同时引入spring-cloud-context包,我们看一下这个包下的spring.factories文件:
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.cloud.bootstrap.BootstrapApplicationListener,\
org.springframework.cloud.bootstrap.LoggingSystemShutdownListener,\
org.springframework.cloud.context.restart.RestartListener
可以看到这里引入了BootstrapApplicationListener类。SpringApplication的run方法执行时,当执行到prepareEnvironment环境准备时,会广播ApplicationEnvironmentPreparedEvent事件,此时BootstrapApplicationListener监听到这个事件,开始执行自己的代码:
@Override
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) {
context = bootstrapServiceContext(environment, event.getSpringApplication(),
configName);
event.getSpringApplication()
.addListeners(new CloseContextOnFailureApplicationListener(context));
}
apply(context, event.getSpringApplication(), environment);
}
这里主要是创建了一个新的上下文context,我们看一下bootstrapServiceContext这个方法:
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:}");
String configAdditionalLocation = environment
.resolvePlaceholders("${spring.cloud.bootstrap.additional-location:}");
Map<String, Object> bootstrapMap = new HashMap<>();
bootstrapMap.put("spring.config.name", configName);
bootstrapMap.put("spring.main.web-application-type", "none");
if (StringUtils.hasText(configLocation)) {
bootstrapMap.put("spring.config.location", configLocation);
}
if (StringUtils.hasText(configAdditionalLocation)) {
bootstrapMap.put("spring.config.additional-location",
configAdditionalLocation);
}
bootstrapProperties.addFirst(
new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
for (PropertySource<?> source : environment.getPropertySources()) {
if (source instanceof StubPropertySource) {
continue;
}
//老的放进新的里
bootstrapProperties.addLast(source);
}
SpringApplicationBuilder builder = new SpringApplicationBuilder()
.profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
.environment(bootstrapEnvironment)
.registerShutdownHook(false).logStartupInfo(false)
.web(WebApplicationType.NONE);
final SpringApplication builderApplication = builder.application();
if (builderApplication.getMainApplicationClass() == null) {
builder.main(application.getMainApplicationClass());
}
if (environment.getPropertySources().contains("refreshArgs")) {
builderApplication
.setListeners(filterListeners(builderApplication.getListeners()));
}
builder.sources(BootstrapImportSelectorConfiguration.class);
final ConfigurableApplicationContext context = builder.run();
context.setId("bootstrap");
addAncestorInitializer(application, context);
bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
return context;
}
代码比较长,简单分析一下
-
首先是创建一个新的环境,将里面的属性源删除干净
-
放入
spring.config.name=bootstrap
的属性源,如果有spring.config.location
和spring.config.additional-location
也一起放入,封装成一个MapPropertySource
属性源name
是bootstrap
,放进新环境属性源里,然后把老的环境属性源放入到新的里 -
创建一个
SpringApplicationBuilder
,就是建造者模式,里面就是创建SpringApplication
的,然后设置一些属性,是一个最简单的一般上下文设置,然后设置一个配置文件BootstrapImportSelectorConfiguration
。public SpringApplicationBuilder(Class<?>... sources) { this.application = createSpringApplication(sources); } protected SpringApplication createSpringApplication(Class<?>... sources) { return new SpringApplication(sources); }
-
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. this.context = build().run(args); } } return this.context; } public SpringApplication build() { return build(new String[0]); } public SpringApplication build(String... args) { configureAsChildIfNecessary(args); this.application.addPrimarySources(this.sources); return this.application; }
最终还是调用SpringApplication的run方法,这里primarySources添加了前面注册进去的BootstrapImportSelectorConfiguration配置类。
总结
在springcloud环境下会创建一个应用程序上下文,实际上这个上下文是主应用上下文的parent。他们共享同一个环境信息environment。配置名使用bootstrap(和application.yml区别)。同时该上下文添加了一个BootstrapImportSelectorConfiguration配置类。我们后面分析。
本文地址:https://blog.csdn.net/qq_19414183/article/details/112238697