spring boot 配置文件加载顺序问题

spring boot 配置文件加载顺序问题

基于spring boot 2.7.x

一般场景

文件加载顺序bootstrap.properties > bootstrap.yml > application.properties > application.yml,而配置生效的顺序application>bootstrap, 即文件加载顺序优先级越低,则配置生效优先级越高。【注意, application-dev.properties > application.properties 配置顺序】当需要覆盖默认配置时,只需要在 application配置文件上覆盖就行

特殊场景

在自定义了 PropertySourceLocator 后, 比较常见的是 使用配置中心,无论是 Nacos还是 使用 spring cloud config 配置中心,这时候就要注意了,单纯的在 本地的 application.properties下覆盖有可能是不生效的

首先看下是如何加载的,看源码 PropertySourceBootstrapConfiguration

PropertySourceBootstrapConfiguration
  • 通过 autowired 注入 PropertySourceLocator 实现类
  • 分别在 ContextRefreshedEvent 事件 和 初始化时进行 PropertySourceLocator 拉取自定义配置
java 复制代码
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(PropertySourceBootstrapProperties.class)
public class PropertySourceBootstrapConfiguration implements ApplicationListener<ContextRefreshedEvent>,
		ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {

	/**
	 * Bootstrap property source name.
	 */
	public static final String BOOTSTRAP_PROPERTY_SOURCE_NAME = BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME
			+ "Properties";

	private static Log logger = LogFactory.getLog(PropertySourceBootstrapConfiguration.class);

	private int order = Ordered.HIGHEST_PRECEDENCE + 10;

	@Autowired(required = false)
	private List<PropertySourceLocator> propertySourceLocators = new ArrayList<>();

	@Autowired
	private PropertySourceBootstrapProperties bootstrapProperties;

	@Override
	public int getOrder() {
		return this.order;
	}

	public void setPropertySourceLocators(Collection<PropertySourceLocator> propertySourceLocators) {
		this.propertySourceLocators = new ArrayList<>(propertySourceLocators);
	}

	@Override
	public void initialize(ConfigurableApplicationContext applicationContext) {
		if (!bootstrapProperties.isInitializeOnContextRefresh() || !applicationContext.getEnvironment()
				.getPropertySources().contains(BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
			doInitialize(applicationContext);
		}
	}

	private void doInitialize(ConfigurableApplicationContext applicationContext) {
		List<PropertySource<?>> composite = new ArrayList<>();
		AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
		boolean empty = true;
		ConfigurableEnvironment environment = applicationContext.getEnvironment();
        // 遍历 自定义 locator 加载配置
		for (PropertySourceLocator locator : this.propertySourceLocators) {
			Collection<PropertySource<?>> source = locator.locateCollection(environment);
			if (source == null || source.size() == 0) {
				continue;
			}
			List<PropertySource<?>> sourceList = new ArrayList<>();
			for (PropertySource<?> p : source) {
				if (p instanceof EnumerablePropertySource) {
					EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) p;
					sourceList.add(new BootstrapPropertySource<>(enumerable));
				}
				else {
					sourceList.add(new SimpleBootstrapPropertySource(p));
				}
			}
			logger.info("Located property source: " + sourceList);
             //将自定义的 PropertySource 放入 composite 列表
			composite.addAll(sourceList);
			empty = false;
		}
		if (!empty) {
            // propertySources 是已经排好序的,即取配置的顺序: application > bootstrap
			MutablePropertySources propertySources = environment.getPropertySources();
			String logConfig = environment.resolvePlaceholders("${logging.config:}");
			LogFile logFile = LogFile.get(environment);
			for (PropertySource<?> p : environment.getPropertySources()) {
				if (p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
					propertySources.remove(p.getName());
				}
			}
            // 这个地方就要根据条件将自定义propertySource 有序放入 MutablePropertySources
			insertPropertySources(propertySources, composite);
			reinitializeLoggingSystem(environment, logConfig, logFile);
			setLogLevels(applicationContext, environment);
			handleProfiles(environment);
		}
	}


	private void insertPropertySources(MutablePropertySources propertySources, List<PropertySource<?>> composite) {
		MutablePropertySources incoming = new MutablePropertySources();
		List<PropertySource<?>> reversedComposite = new ArrayList<>(composite);
		// Reverse the list so that when we call addFirst below we are maintaining the
		// same order of PropertySources
		// Wherever we call addLast we can use the order in the List since the first item
		// will end up before the rest
        //先倒序
		Collections.reverse(reversedComposite);
		for (PropertySource<?> p : reversedComposite) {
			incoming.addFirst(p);
		}
		PropertySourceBootstrapProperties remoteProperties = new PropertySourceBootstrapProperties();
		Binder.get(environment(incoming)).bind("spring.cloud.config", Bindable.ofInstance(remoteProperties));
		if (!remoteProperties.isAllowOverride()
				|| (!remoteProperties.isOverrideNone() && remoteProperties.isOverrideSystemProperties())) {
            //1.如果远程配置 allowOverride=false 或者
            //2 overrideNone=false && overrideSystemProperties=true
            //则将序号越小的propertySource放在MutablePropertySources中
            //即优先取自定义PropertySourceLocator的配置,然后再本地配置
            //1>2>本地配置
			for (PropertySource<?> p : reversedComposite) {
				if (propertySources.contains(DECRYPTED_PROPERTY_SOURCE_NAME)) {
					propertySources.addAfter(DECRYPTED_PROPERTY_SOURCE_NAME, p);
				}
				else {
					propertySources.addFirst(p);
				}
			}
			return;
		}
        // 如果:overrideNone=true,即优先取本地配置,然后再自定义PropertySourceLocator的配置
        //本地配置> 1>2
		if (remoteProperties.isOverrideNone()) {
			for (PropertySource<?> p : composite) {
                 //将自定义配置放在最后
				propertySources.addLast(p);
			}
			return;
		}
		if (propertySources.contains(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME)) {
			if (!remoteProperties.isOverrideSystemProperties()) {
				for (PropertySource<?> p : reversedComposite) {
					propertySources.addAfter(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, p);
				}
			}
			else {
				for (PropertySource<?> p : composite) {
					propertySources.addBefore(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, p);
				}
			}
		}
		else {
			for (PropertySource<?> p : composite) {
				propertySources.addLast(p);
			}
		}
	}

	/*
	 * The ConextRefreshedEvent gets called at the end of the boostrap phase after config
	 * data is loaded during bootstrap. This will run and do an "initial fetch" of
	 * configuration data during bootstrap but before the main applicaiton context starts.
	 */
	@Override
	public void onApplicationEvent(ContextRefreshedEvent event) {
		if (bootstrapProperties.isInitializeOnContextRefresh()
				&& event.getApplicationContext() instanceof ConfigurableApplicationContext) {
			if (((ConfigurableApplicationContext) event.getApplicationContext()).getEnvironment().getPropertySources()
					.contains(BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
				doInitialize((ConfigurableApplicationContext) event.getApplicationContext());
			}
		}
	}

}

分析完,总结一下,在使用自定义 PropertySourceLocator 场景下

  • 如果远程配置 allowOverride=false 或者 2 overrideNone=false && overrideSystemProperties=true
    • PropertySourceLocator_1 > PropertySourceLocator_2 > 本地配置(application.properties)
  • 如果:overrideNone=true
    • 本地配置(application.properties) > PropertySourceLocator_1 > PropertySourceLocator_2

这里还有一个问题 remoteProperties 注入的问题,如果想覆盖远程使用本地配置,你是这样在本地application.properties 或者 bootstrap.properties 配置的:

properties 复制代码
spring.cloud.config.allow-override=true
spring.cloud.config.override-none=true
spring.cloud.config.override-system-properties=false

发现还不生效,这是因为 remoteProperties 是通过注入 自定义PropertySourceLocator 的配置,而非本地application.properties 或者 bootstrap.properties

所以只要将这些配置放在远程配置中心,或者其他自定义 PropertySourceLocator 加载的配置中即可

相关推荐
曼岛_28 分钟前
[Java实战]Spring Boot整合RabbitMQ:实现异步通信与消息确认机制(二十七)
java·spring boot·java-rabbitmq
键盘不能没有CV键36 分钟前
【SpringBoot】✈️整合飞书群机器人发送消息
spring boot
计算机毕设定制辅导-无忧学长2 小时前
Spring Boot 与 RabbitMQ 的深度集成实践(一)
spring boot·rabbitmq·java-rabbitmq
编程、小哥哥3 小时前
Java面试场景:从音视频到AI应用的技术探讨
spring boot·spring cloud·微服务·音视频·java面试·ai应用
计算机毕设定制辅导-无忧学长3 小时前
Spring Boot 与 RabbitMQ 的深度集成实践(二)
spring boot·rabbitmq·java-rabbitmq
Code哈哈笑4 小时前
【图书管理系统】用户注册系统实现详解
数据库·spring boot·后端·mybatis
用手手打人4 小时前
SpringBoot(一)--- Maven基础
spring boot·后端·maven
不会就选C.5 小时前
【开源分享】健康饮食管理系统(双端+论文)
java·spring boot·开源·毕业设计
编程、小哥哥6 小时前
Java求职者面试:从Spring Boot到微服务的技术点解析
java·spring boot·redis·微服务·spring security·高并发·面试题
Code哈哈笑6 小时前
【基于Spring Boot 的图书购买系统】深度讲解 用户注册的前后端交互,Mapper操作MySQL数据库进行用户持久化
数据库·spring boot·后端·mysql·mybatis·交互