一文搞懂Spring Boot中application.yml以及application-xx.yml配置文件的加载

Spring Boot 2.6.x源码系列:
一文搞懂Spring Boot中java -jar启动jar包的原理
一文搞懂SpringBoot启动流程及自动配置
一文搞懂SpringBoot内嵌的Tomcat
一文搞懂SpringApplication对象的构建及spring.factories的加载时机
一文搞懂Spring Boot中application.yml以及application-xx.yml配置文件的加载

在Spring Boot中何时、如何加载application.yml以及application-xx.yml配置文件?

前置条件:
创建好配置文件application-dev.yml
并在application.yml配置spring.profiles.active属性
在这里插入图片描述

1、我们进入SpringApplication的run方法。会发现配置环境的加载是在ApplicationContext创建之前,prepareEnvironment方法执行即准备环境之时。

public ConfigurableApplicationContext run(String... args) {
		......	
		ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
	    ......
	    context = createApplicationContext();	
		......
	}

准备环境prepareEnvironment方法。

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
		// Create and configure the environment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		ConfigurationPropertySources.attach(environment);
		listeners.environmentPrepared(bootstrapContext, environment);
		DefaultPropertiesPropertySource.moveToEnd(environment);
		Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
				"Environment prefix cannot be set via properties.");
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = convertEnvironment(environment);
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

2、接下来我们对象上面步骤进行详细追踪。getOrCreateEnvironment获取ConfigurableEnvironment对象。ConfigurableEnvironment是一个大多数Environment类型要实现的接口,提供了设置Active和Default的Profiles的方法, 允许客户端通过ConfigurablePropertyResolver来设置和验证所需的属性,自定义转换服务(conversion service),以及提供了获取属性源的方法。下面是ConfigurableEnvironment源码:

public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
	/**
	 * 设置Active的配置文件,任何现有的配置文件(profiles)都会被替换为参数中的配置文件(profiles)
	 */	
	void setActiveProfiles(String... profiles);

	/**
	 * 将参数配置文件(profile)添加到已激活的配置文件集(active profiles)中
	 */
	void addActiveProfile(String profile);

	/**
	 * 没有其他配置文件通过setActiveProfiles显式激活,则指定默认情况下激活的配置文件集
	 */
	void setDefaultProfiles(String... profiles);
	
	/**
	 * 获取可变形式的当前环境的PropertySources,从而能在解析环境时操作PropertySources,			         
	 * 如addFirst,addLast,addBefore,addAfter等方法。这样可以确保用户自定义的PropertySources的搜索优先级高于默认的属性源。
	 */
	MutablePropertySources getPropertySources();

    /**
	 * 获取系统属性
	 */
	Map<String, Object> getSystemProperties();

	/**
	 * 获取系统环境
	 */
	Map<String, Object> getSystemEnvironment();
	
	/**
	 * 将给定父环境的活动配置文件、默认配置文件和属性源附加到此(子)环境各自的集合中
	 * 对于父实例和子实例中存在的任何同名PropertySource实例,子实例将被保留,父实例将被丢弃。
	 * 这样做的效果是允许子级重写属性源,并避免通过常见属性源类型(例如系统环境和系统属性)进行冗余搜索。
	 */ 	
	void merge(ConfigurableEnvironment parent);

}

我们通过getOrCreateEnvironment源码发现,可返回的ConfigurableEnvironment对象有ApplicationServletEnvironment,ApplicationReactiveWebEnvironment,ApplicationEnvironment,他们分别是SERVLET、REACTIVE、NONE等WebApplicationType的的环境。

private ConfigurableEnvironment getOrCreateEnvironment() {
		if (this.environment != null) {
			return this.environment;
		}
		switch (this.webApplicationType) {
		case SERVLET:
			return new ApplicationServletEnvironment();
		case REACTIVE:
			return new ApplicationReactiveWebEnvironment();
		default:
			return new ApplicationEnvironment();
		}
	}

3、configureEnvironment方法,它是一个模板方法,按顺序委托给了configurePropertySources方法和configureProfiles方法。重写这个方法可以实现控制自定义环境,重写configurePropertySources或configureProfiles可以实现对属性源或配置文件进行细粒度控制。

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
		if (this.addConversionService) {
		    //配置环境设置转换服务(默认情况下配置了适合大多数Spring Boot application的转换器和格式化程序。)
			environment.setConversionService(new ApplicationConversionService());
		}
		//在应用程序环境中配置属性源(添加、或删除或重排序属性源)
		configurePropertySources(environment, args);
		//为应用环境配置active的配置文件,也可以在配置文件处理期间,通过 spring.profiles.active 激活其他配置文件
		configureProfiles(environment, args);
	}

4、ConfigurationPropertySources.attach(environment) 这一步将ConfigurationPropertySource附加到指定的ConfigurableEnvironment。

	public static void attach(Environment environment) {
		//提供的environment必须是ConfigurableEnvironment类型
		Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
		//从ConfigurableEnvironment对象中获取MutablePropertySources(可变的属性源)对象
		MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();
		//从MutablePropertySources根据PropertySource适配器的名称configurationProperties来获取附加的属性源
		PropertySource<?> attached = getAttached(sources);
		if (attached == null || !isUsingSources(attached, sources)) {
		    //初始化附加的属性源为ConfigurationPropertySourcesPropertySource对象,从而可以暴露ConfigurationPropertySource实例
		    //以便它们能与PropertyResolver一起使用或将其添加到环境中
			attached = new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME,
					new SpringConfigurationPropertySources(sources));
		}
		//若MutablePropertySources对象的propertySourceList有configurationProperties,将其移除
		sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
		//将附加的名称为configurationProperties的属性源添加到propertySourceList的首部(即将其配置为最高优先级的属性源)。
		sources.addFirst(attached);
	}

5、listeners.environmentPrepared(bootstrapContext, environment) 这一步是通过SpringApplicationRunListeners来广播ApplicationEnvironmentPreparedEvent事件,表明SpringApplication启动且环境首次可供检查和修改,此时环境已准备好但是ApplicationContext容器还未创建。本文的重点也即配置文件的加载就在这一部分。Spring Boot中的监听器这部分源码出于篇幅原因我们就不追了,大家有兴趣可参考我的另一篇文章一文搞懂Spring Boot 事件监听机制。我们重点关注application.xml配置文件的加载部分。
当执行到SimpleApplicationEventMulticaster的doInvokeListener方法时实际进入的是EnvironmentPostProcessorApplicationListener中的onApplicationEvent方法。

public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationEnvironmentPreparedEvent) {
			onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
		}
		if (event instanceof ApplicationPreparedEvent) {
			onApplicationPreparedEvent();
		}
		if (event instanceof ApplicationFailedEvent) {
			onApplicationFailedEvent();
		}
	}

此时我们的事件是ApplicationEnvironmentPreparedEvent,因此进入onApplicationEnvironmentPreparedEvent方法。

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
		//从ApplicationEnvironmentPreparedEvent 事件中获取ConfigurableEnvironment对象,该对象里包含了propertySourceList,我们重点关注该属性源列表中属性源的变化
		ConfigurableEnvironment environment = event.getEnvironment();
		//从ApplicationEnvironmentPreparedEvent 事件中获取SpringApplication 对象
		SpringApplication application = event.getSpringApplication();
		//在此会获取一系列EnvironmentPostProcessor,我们重点关注ConfigDataEnvironmentPostProcessor,ConfigDataEnvironmentPostProcessor该配置环境后置处理器会加载配置数据并将其应用到环境中
		for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(application.getResourceLoader(),
				event.getBootstrapContext())) {
			postProcessor.postProcessEnvironment(environment, application);
		}
	}

进入ConfigDataEnvironmentPostProcessor对象的postProcessEnvironment方法,在该方法中添加了配置数据的后处理环境。

void postProcessEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
			Collection<String> additionalProfiles) {
		try {
			this.logger.trace("Post-processing environment to add config data");
			resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
			getConfigDataEnvironment(environment, resourceLoader, additionalProfiles).processAndApply();
		}
		catch (UseLegacyConfigProcessingException ex) {
			this.logger.debug(LogMessage.format("Switching to legacy config file processing [%s]",
					ex.getConfigurationProperty()));
			configureAdditionalProfiles(environment, additionalProfiles);
			postProcessUsingLegacyApplicationListener(environment, resourceLoader);
		}
	}

继续跟踪上述getConfigDataEnvironment方法获取配置数据的环境,在该方法中构建了ConfigDataEnvironment实例,

ConfigDataEnvironment getConfigDataEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
			Collection<String> additionalProfiles) {
		return new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, environment, resourceLoader,
				additionalProfiles, this.environmentUpdateListener);
	}

构建时初始化了一个重要的属性ConfigDataLoaders,初始化ConfigDataLoaders时初始化了ConfigDataLoader列表,ConfigDataLoader是一个接口提供了用来加载给定ConfigDataResource的配置数据的load方法;

public interface ConfigDataLoader<R extends ConfigDataResource> {
    //返回此context是否可以加载指定资源resource
	default boolean isLoadable(ConfigDataLoaderContext context, R resource) {
		return true;
	}
	//从给定的resource加载配置数据
	ConfigData load(ConfigDataLoaderContext context, R resource)
			throws IOException, ConfigDataResourceNotFoundException;

}

ConfigDataEnvironment中包含了配置数据的加载位置

optional:classpath:/;optional:classpath:/config/
optional:file:./;optional:file:./config/;optional:file:./config/*/

执行完getConfigDataEnvironment方法获取到ConfigDataEnvironment后就进入了processAndApply方法,debug时重点关注OriginTrackedMapPropertySource {name=‘xxx’’}

void processAndApply() {
		//创建一个ConfigDataImporter实例
		ConfigDataImporter importer = new ConfigDataImporter(this.logFactory, this.notFoundAction, this.resolvers,
				this.loaders);	
		registerBootstrapBinder(this.contributors, null, DENY_INACTIVE_BINDING);
		//初始化ConfigDataEnvironmentContributors ,即在没有激活上下文的情况下处理初始化配置环境contributors 
		//初始化时通过ConfigDataImporter加载了配置文件[application.yml]
		ConfigDataEnvironmentContributors contributors = processInitial(this.contributors, importer);
		//创建ConfigDataActivationContext对象,从初始化的ConfigDataEnvironmentContributors创建配置数据并激活context
		ConfigDataActivationContext activationContext = createActivationContext(
				contributors.getBinder(null, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE));
		//使用初始化的ConfigDataActivationContext处理ConfigDataEnvironmentContributors 
		contributors = processWithoutProfiles(contributors, importer, activationContext);
		//从ConfigDataEnvironmentContributors中推导出配置文件,并返回带有特定配置文件的新ConfigDataActivationContext
		activationContext = withProfiles(contributors, activationContext);
		//使用ConfigDataActivationContext处理ConfigDataEnvironmentContributors,此时这一步到这一步会通过ConfigDataImporter加载配置文件[application-dev.yml]
		contributors = processWithProfiles(contributors, importer, activationContext);
		//在这一步会将获取的ActiveProfiles添加ConfigurableEnvironment环境中。
		applyToEnvironment(contributors, activationContext, importer.getLoadedLocations(),
				importer.getOptionalLocations());
	}

综上所述应是在初始化ConfigDataEnvironmentContributors 时加载了application.yml配置文件,并从初始化的ConfigDataEnvironmentContributors 激活了上下文,通过激活的上下文来处理ConfigDataEnvironmentContributors,从ConfigDataEnvironmentContributors中推导出配置文件application.yml并返回带有application.yml的新的上下文ConfigDataActivationContext ,通过该上下文处理ConfigDataEnvironmentContributorsq,从而加载配置文件application-dev.yml。
6、DefaultPropertiesPropertySource.moveToEnd(environment) 这一步将PropertySource列表中的名称为defaultProperties属性源移到最后,使之成为ConfigurableEnvironment的最后一个源。保证最后优先用定义好的配置属性源。
7、绑定环境到SpringApplication。

8、ConfigurationPropertySources.attach(environment)将ConfigurationPropertySources附加到ConfigurableEnvironment。

  • 19
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值