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

相关推荐
皮皮林55121 分钟前
拒绝写重复代码,试试这套开源的 SpringBoot 组件,效率翻倍~
java·spring boot
用户908324602733 天前
Spring AI 1.1.2 + Neo4j:用知识图谱增强 RAG 检索(上篇:图谱构建)
java·spring boot
用户8307196840823 天前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq
Java水解3 天前
Spring Boot 视图层与模板引擎
spring boot·后端
Java水解3 天前
一文搞懂 Spring Boot 默认数据库连接池 HikariCP
spring boot·后端
洋洋技术笔记4 天前
Spring Boot Web MVC配置详解
spring boot·后端
初次攀爬者4 天前
Kafka 基础介绍
spring boot·kafka·消息队列
用户8307196840824 天前
spring ai alibaba + nacos +mcp 实现mcp服务负载均衡调用实战
spring boot·spring·mcp
Java水解4 天前
SpringBoot3全栈开发实战:从入门到精通的完整指南
spring boot·后端
初次攀爬者5 天前
RocketMQ在Spring Boot上的基础使用
java·spring boot·rocketmq