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

相关推荐
2601_953720826 小时前
【计算机毕业设计】基于Vue与Spring Boot的高校兼职信息服务平台设计与实现
spring boot·后端·课程设计
evans在进步8 小时前
Spring Boot 核心机制详解:可执行 JAR、CORS、静态资源与配置绑定
spring boot·后端·jar
Sayuanni%38 小时前
SpringBoot 从注解到源码:核心知识点总结
java·spring boot·后端
m0_5873830010 小时前
社区家政系统开发实战:从需求分析到上线部署全指南
java·spring boot·spring·需求分析
凤山老林11 小时前
动态 i18n 体系落地:Spring Boot 多租户热加载与前后端协同实践
java·spring boot·后端·i18n
【赫兹威客】浩哥11 小时前
基于SpringBoot+Vue3的健身运动管理系统|WebSocket实时消息 健身房毕设项目
spring boot·websocket·课程设计
凤山老林11 小时前
数据库读写分离与动态路由实战:Spring Boot + ShardingSphere-JDBC 生产配置
数据库·spring boot·后端·分库分表·sharding-jdbc
RuoyiOffice12 小时前
SpringBoot3+Vue3 代码生成器深拆:元数据 IR、模板编译与安全同步
spring boot·vue3·mybatis plus·代码生成器·spring boot 3·velocity·元数据驱动
paopaokaka_luck13 小时前
基于springboot3+vue3的支教志愿者管理系统(AI问答、协同过滤算法、Echarts图形化分析)
spring boot·echarts
2601_9638701814 小时前
【计算机毕业设计】基于Vue+Spring Boot的助农电商系统的设计与实现
spring boot·后端·课程设计