在看eureka server源码的时候发现spring的refresh方法被执行了两次!开始还以为是自己代码写错了,经过调试发现spring cloud竟然自己启动了一个单独的容器;下面我们来分析一下代码原理
接上一篇讲解【springboot事件管理机制】文章 https://blog.csdn.net/Aqu415/article/details/126197702
spring cloud框架提供了的BootstrapApplicationListener会在SpringApplication的构造方法里被解析并缓存进入容器
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) {
// @A
context = bootstrapServiceContext(environment, event.getSpringApplication(),
configName);
event.getSpringApplication()
.addListeners(new CloseContextOnFailureApplicationListener(context));
}
apply(context, event.getSpringApplication(), environment);
}
@A:spring cloud 另起炉灶,启动新的spring容器,确切的说是SpringApplication
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:}");
... 省略...
// @A
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();
... 省略...
// @B
builder.sources(BootstrapImportSelectorConfiguration.class);
final ConfigurableApplicationContext context = builder.run();
// @C
addAncestorInitializer(application, context);
... 省略...
bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
return context;
}
@A:构建一个SpringApplicationBuilder对象
@B:设置springapplication的sources属性【偷天换日】,比较精妙;下面会分析
@C:将spring cloud的容器和main方法启动的容器设置父子关系
private void addAncestorInitializer(SpringApplication application,
ConfigurableApplicationContext context) {
boolean installed = false;
... 省略...
if (!installed) {
// @A
application.addInitializers(new AncestorInitializer(context));
}
}
@A:向application的Initializer里放了一个AncestorInitializer对象,application即springboot原生的应用对象;在外层主流程的prepareContext会调用所有的Initializer的initialize方法
图中
1、@A是spring cloud的BootstrapApplicationListener执行时机
2、@B是所有Initializer执行时机
initialize 方法
@Override
public void initialize(ConfigurableApplicationContext context) {
while (context.getParent() != null && context.getParent() != context) {
context = (ConfigurableApplicationContext) context.getParent();
}
reorderSources(context.getEnvironment());
// @A
new ParentContextApplicationContextInitializer(this.parent)
.initialize(context);
}
@A:设置springcloud容器和标准springboot容器的父子关系
上面讲了springcloudi借助BootstrapApplicationListener将BootstrapImportSelectorConfiguration注入到容器中;
使用的如下代码:
builder.sources(BootstrapImportSelectorConfiguration.class);
todo~~