SpringBoot 配置文件加载

配置文件加载

引入

在开发配置同步工具时,遇到一个问题:在resource下增加自定义yml后,需要在bootstrap.yml 中指定配置文件名称;并且需要同时指定默认配置文件名称 application.yml,不然就不扫了

yml 复制代码
spring:
  config:
    name: config-sysnchronous,application

自此引发了几个思考:

  • 为什么要在bootstrap.yml 里配置?
  • 配置后为什么 application.yml 就失效了?

问题研究

首先,需要明白这两个文件都是如何被SpringBoot加载的

监听器与事件

上面这个类图描述了加载bootstrap.ymlapplication.yml设计的监听器和监听器监听的事件。

bootstrap.yml

  • 监听器:BootstrapApplicationListener
  • 事件:ApplicationEnvironmentPreparedEvent
  • 核心代码:
java 复制代码
@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);
		}
		apply(context, event.getSpringApplication(), environment);
	}

application.yml

  • 监听器 :ConfigFileApplicationListener
  • 事件:ApplicationPreparedEvent
  • 核心代码:
java 复制代码
	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationEnvironmentPreparedEvent) {
			onApplicationEnvironmentPreparedEvent(
					(ApplicationEnvironmentPreparedEvent) event);
		}
		if (event instanceof ApplicationPreparedEvent) {
			onApplicationPreparedEvent(event);
		}
	}

事件顺序

在SprintBoot的启动方法中,优先进行了 ApplicationEnvironmentPreparedEvent类型事件的触发,再进行了ApplicationPreparedEvent 类型事件的触发:

java 复制代码
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);
            // ApplicationEnvironmentPreparedEvent 类型事件触发
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
            // ApplicationPreparedEvent 类型事件触发
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
        ...
			
	}

监听器执行类EventPublishingRunListener分发不同类型事件

java 复制代码
	@Override
	public void environmentPrepared(ConfigurableEnvironment environment) {
		this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
				this.application, this.args, environment));
	}
java 复制代码
	@Override
	public void contextLoaded(ConfigurableApplicationContext context) {
		for (ApplicationListener<?> listener : this.application.getListeners()) {
			if (listener instanceof ApplicationContextAware) {
				((ApplicationContextAware) listener).setApplicationContext(context);
			}
			context.addApplicationListener(listener);
		}
		this.initialMulticaster.multicastEvent(
				new ApplicationPreparedEvent(this.application, this.args, context));
	}

监听器顺序

bootstrap.yml对应的监听器监听ApplicationEnvironmentPreparedEvent类型事件,application.yml对应监听器监听 ApplicationEvent类型事件,包含了ApplicationEnvironmentPreparedEvent,并根据这两个事件类型有不同处理方式。那么ApplicationEnvironmentPreparedEvent事件是如何保证被BootstrapApplicationListener优先执行的呢?

主要通过类图中的Order类

这两个监听器都继承了Ordered类并通过重新getOrdered 方法设置自己的顺序:

BootstrapApplicationListener

java 复制代码
	private int order = DEFAULT_ORDER;
	public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 5;	
@Override
	public int getOrder() {
		return this.order;
	}

ConfigFileApplicationListener

java 复制代码
	private int order = DEFAULT_ORDER;
	public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 10;
@Override
	public int getOrder() {
		return this.order;
	}

在SpringBoot进行初始化加载Listenner类是根据order排序:

java 复制代码
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		...
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		...
	}
java 复制代码
	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<>(
				SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

其中 AnnotationAwareOrderComparator.sort(instances) 使用的对比类 AnnotationAwareOrderComparator中根据Order类顺序进行排序。

加载顺序总结

bootstrap.yml的加载事件ApplicationEnvironmentPreparedEvent在Springboot启动时优先被创建分发并被优先级较高的BootstrapApplicationListener类监听,application.yml的加载事件ApplicationPreparedEvent 在SprintBoot中在 ApplicationEnvironmentPreparedEvent类事件后被分发并被监听。

默认配置与自定义配置加载

ConfigFileApplicationListener加载配置文件时,如果在 bootstrap.yml中配置了配置文件,会影响默认配置文件application.yml的加载:

yml 复制代码
spring:
  config:
    name: config-sysnchronous,application
java 复制代码
    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);
				return asResolvedSet(property, null);
			}
			return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
		}

此时,bootstrap.yml 中的内容已经被加载入enviroment,如果在 bootstrap.yml中配置了配置文件,默认配置文件application.yml就不会被加载

问题总结

  • 为什么要在bootstrap.yml 里配置?

SprintBoot 优先加载 bootstrap.yml的内容,这个文件通常包含一些用于系统环境的配置,后续加载应用配置文件时会根据该文件的配置信息进行判断。

  • 配置后为什么 application.yml 就失效了?

根据源码,默认配置文件与bootstrap.yml中配置的配置文件名为或关系,所以配置了就失效了。

相关推荐
阿伟*rui3 小时前
配置管理,雪崩问题分析,sentinel的使用
java·spring boot·sentinel
paopaokaka_luck5 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
Yaml47 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~7 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616887 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
程序媛小果8 小时前
基于java+SpringBoot+Vue的旅游管理系统设计与实现
java·vue.js·spring boot
AskHarries10 小时前
Spring Boot集成Access DB实现数据导入和解析
java·spring boot·后端
2401_8576226610 小时前
SpringBoot健身房管理:敏捷与自动化
spring boot·后端·自动化
程序员阿龙11 小时前
基于SpringBoot的医疗陪护系统设计与实现(源码+定制+开发)
java·spring boot·后端·医疗陪护管理平台·患者护理服务平台·医疗信息管理系统·患者陪护服务平台
前 方11 小时前
若依入门案例
java·spring boot·maven