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中配置的配置文件名为或关系,所以配置了就失效了。

相关推荐
haciii2 小时前
Spring Boot启动源码深度分析 —— 新手也能看懂的原理剖析
spring boot
泉城老铁8 小时前
Spring Boot对接抖音获取H5直播链接详细指南
spring boot·后端·架构
后端小张1 天前
基于飞算AI的图书管理系统设计与实现
spring boot
考虑考虑2 天前
Jpa使用union all
java·spring boot·后端
阿杆2 天前
同事嫌参数校验太丑,我直接掏出了更优雅的 SpEL Validator
java·spring boot·后端
昵称为空C3 天前
SpringBoot3 http接口调用新方式RestClient + @HttpExchange像使用Feign一样调用
spring boot·后端
麦兜*3 天前
MongoDB Atlas 云数据库实战:从零搭建全球多节点集群
java·数据库·spring boot·mongodb·spring·spring cloud
麦兜*3 天前
MongoDB 在物联网(IoT)中的应用:海量时序数据处理方案
java·数据库·spring boot·物联网·mongodb·spring
汤姆yu3 天前
基于springboot的毕业旅游一站式定制系统
spring boot·后端·旅游
计算机毕业设计木哥3 天前
计算机毕设选题推荐:基于Java+SpringBoot物品租赁管理系统【源码+文档+调试】
java·vue.js·spring boot·mysql·spark·毕业设计·课程设计