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

相关推荐
小咕聊编程30 分钟前
【含文档+PPT+源码】基于spring boot的固定资产管理系统
java·spring boot·后端
马尚道1 小时前
SpringBoot开发双11商品服务系统 | 已完结
spring boot
韩立学长1 小时前
【开题答辩实录分享】以《自然灾害隐患点管理信息系统》为例进行答辩实录分享
数据库·spring boot
我命由我123451 小时前
Spring Cloud - Spring Cloud 注册中心与服务提供者(Spring Cloud Eureka 概述、微服务快速入门、微服务应用实例)
java·spring boot·spring·spring cloud·微服务·eureka·java-ee
一线大码2 小时前
SpringBoot 优雅实现接口的多实现类方式
java·spring boot·后端
Q_Q19632884753 小时前
python+uniapp基于微信小程序的助眠小程序
spring boot·python·小程序·django·flask·uni-app·node.js
摇滚侠3 小时前
Spring Boot 3零基础教程,WEB 开发 Thymeleaf 属性优先级 行内写法 变量选择 笔记42
java·spring boot·笔记
摇滚侠3 小时前
Spring Boot 3零基础教程,WEB 开发 Thymeleaf 总结 热部署 常用配置 笔记44
java·spring boot·笔记
十年小站3 小时前
一、新建一个SpringBoot3项目
java·spring boot
程序员阿达3 小时前
开题报告之基于SpringBoot框架的路面故障信息上报系统设计与实现
java·spring boot·后端