目录
父容器加载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监听器才是本次的重点

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
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需要的配置,接下来从配置文件加载和配置类加载来看父子容器的区别
首先它处理两个事件,我们只关注环境加载事件

跟代码

这里在路径下,根据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
getSearchNames() { - if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
- String property = this.environment.getProperty(CONFIG_NAME_PROPERTY);
- Set
names = asResolvedSet(property, null); - names.forEach(this::assertValidConfigName);
- return names;
- }
- return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
- }
在启动类中会设置@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