目录
父容器加载BootstrapApplicationListener
配置文件加载ConfigFileApplicationListener
配置类加载BootstrapImportSelectorConfiguration
看默认情况,不论使用参数更改的情况(spring.config.name)
纯粹的SpringBoot主要就是自动配置,加载application.properties和spring.factories中key为EnableAutoConfiguration的配置类进行bean的自动装配,并不涉及bootstrap,bootstrap配置文件是SpringCloud扩展的配置文件,优先于application.properties加载,应用场景例如nacos配置中心的配置属性须在bootstrap.properties中配置
下边来看源码是怎么优先加载的
跟随启动类到run方法org.springframework.boot.SpringApplication#run,在prepareEnvironment方法中构建环境变量对象和加载配置文件
org.springframework.boot.SpringApplication#prepareEnvironment
创建Environment对象,通过各种监听器来加载不同的配置变量
监听器列表debug如下,第四位ConfigFile监听器即就是加载配置文件(不论是bootstrap还是application都是由它加载)的监听器,优先级最高的Bootstrap监听器才是本次的重点
父容器加载BootstrapApplicationListener
org.springframework.cloud.bootstrap.BootstrapApplicationListener
只处理EnvironmentPrepare事件
创建了一个属于bootstrap的新容器
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
// 是否开启bootstrap配置,默认开启
if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
true)) {
return;
}
// 标志位,如果当前环境变量有bootstrap标识,则跳过本类处理,这个标志位往下看就行
// BOOTSTRAP_PROPERTY_SOURCE_NAME = bootstrap
if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
return;
}
ConfigurableApplicationContext context = null;
String configName = environment
.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
// 省略我不懂的代码
// 。。。。
if (context == null) {
// 新创建一个bootstrap的容器
context = bootstrapServiceContext(environment, event.getSpringApplication(),
configName);
event.getSpringApplication()
.addListeners(new CloseContextOnFailureApplicationListener(context));
}
apply(context, event.getSpringApplication(), environment);
}
org.springframework.cloud.bootstrap.BootstrapApplicationListener#bootstrapServiceContext
其中新创建了一个SpringApplication,设置配置文件名为bootstrap,又引入了BootstrapImportSelectorConfiguration配置类,最后执行run方法
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());
}
// 设置容器的环境
// 配置文件名为bootstrap\配置文件地址
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);//配置文件名,默认bootstrap
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);
}
// BOOTSTRAP_PROPERTY_SOURCE_NAME = bootstrap
// 也是上个方法的标志位
bootstrapProperties.addFirst(
new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
for (PropertySource<?> source : environment.getPropertySources()) {
if (source instanceof StubPropertySource) {
continue;
}
bootstrapProperties.addLast(source);
}
// 这里重新使用SpringAoolication的建造器重新构建了一个boot容器
// 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) {
builder.main(application.getMainApplicationClass());
}
if (environment.getPropertySources().contains("refreshArgs")) {
builderApplication
.setListeners(filterListeners(builderApplication.getListeners()));
}
// 为新容器引入BootstrapImportSelectorConfiguration配置类,执行run方法
builder.sources(BootstrapImportSelectorConfiguration.class);
final ConfigurableApplicationContext context = builder.run();
// 设置容器id,父子关系,移除标志位
context.setId("bootstrap");
addAncestorInitializer(application, context);
bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
return context;
}
以上代码,知道了SpringCloud在启动时会新创建一个父容器来加一些Cloud需要的配置,接下来从配置文件加载和配置类加载来看父子容器的区别
配置文件加载ConfigFileApplicationListener
首先它处理两个事件,我们只关注环境加载事件
跟代码
这里在路径下,根据getSearchName返回的文件名进行加载文件
下边逻辑如果当前环境存在参数spring.config.name则使用参数的文件名进行加载,那刚才SpringCloud的新容器设置了参数=boostrap,所以在父容器中会加载bootstrap.properties文件,而在子容器中会使用默认的application.properties进行加载
public static final String CONFIG_NAME_PROPERTY = "spring.config.name";
private static final String DEFAULT_NAMES = "application";
private Set<String> getSearchNames() {
if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
String property = this.environment.getProperty(CONFIG_NAME_PROPERTY);
Set<String> names = asResolvedSet(property, null);
names.forEach(this::assertValidConfigName);
return names;
}
return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
}
配置类加载BootstrapImportSelectorConfiguration
在启动类中会设置@EnableAutoConfiguration和@ComponentScan来自动配置和扫描各种bean,而在父容器创建的时候只引入了BootstrapImportSelectorConfiguration
和EnableAutoConfiguration类似,引入了Selector来进行spring.factories文件配置类的加载
但是与EnableAutoConfiguration不同的是Bootstrap的代码如下,查询的是spring.factories中key为BootstrapConfiguration的配置类
这也是nacos配置中心得在bootstrap.properties中配置的原因,看代码如下
nacos的配置属性类NacosConfigProperties
自动加载前缀为spring.cloud.nacos.config的属性
但是这个NacosConfigProperties类在初始化后会执行@PostConstruct方法
如果bootstrap.properties环境变量未配置nacos配置中心地址,则会默认设置为localhost:8848