SprintBoot系列之加载application.yml文件中的配置过程

4 篇文章 0 订阅
3 篇文章 0 订阅

前言

从SpringMvc开发过度到SpringBoot开发的读者一定可以感受到开发效率大大的提升,其中之一就是自动装配特性,它提供了大量的默认配置,节省了开发人员的时间成本。文章介绍的并不是自动装配的原理,而是其中关于SpringBoot是如何读取Yaml或者Properties配置文件的。

META-INF/spring.factories

要想说清楚SpringBoot如何加载Yaml配置文件,需要先了解spring.factories文件。如下图,SpringBoot的有些jar包中包含了spring.factories文件,这个文件存放了一些程序启动时需要实例化的类。
![在这里插入图片描述](https://img-blog.csdnimg.cn/b7dcbe94fac0477a955de03c5b98ebc7.png
在这里插入图片描述

spring.factories文件存放了接口或者父类,及其配置的相关类。接着,了解一下SpringBoot的SpringFactoriesLoader类的loadFactoryNames方法,它会读取加载的所有jar包类的spring.factories文件中的某个父类,然后返回配置的子类,如以下方法,会返回所有的配置的EnableAutoConfiguration子类。这也是为什么我们可以不用写缺省的配置。

protected List<String> getAutoConfigurations() {
		if (this.autoConfigurations == null) {
			this.autoConfigurations = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,
					this.beanClassLoader);
		}
		return this.autoConfigurations;
	}

在这里插入图片描述
在这里插入图片描述
了解了SpringBoot如何加载spring.factories配置文件中准备实例化的的类,后面就开始介绍SpringBoot是如何读取Yaml配置文件的。

EnvironmentPostProcessorApplicationListener 和 EnvironmentPostProcessor

SpringBoot在启动时,读取所有的ApplicationListener下的配置类。

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener

在这里插入图片描述
当EnvironmentPostProcessorApplicationListener类接收到ApplicationEnvironmentPreparedEvent的事件时,就会调用EnvironmentPostProcessorsFactory类下的getEnvironmentPostProcessors方法来获取所有的EnvironmentPostProcessor下的配置类,并触发这些类的postProcessEnvironment方法。

public class EnvironmentPostProcessorApplicationListener implements SmartApplicationListener, Ordered {
	...
	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationEnvironmentPreparedEvent) {
			onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
		}
		...
	}

	private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
		...
		for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(application.getResourceLoader(),
				event.getBootstrapContext())) {
			postProcessor.postProcessEnvironment(environment, application);
		}
	}

	List<EnvironmentPostProcessor> getEnvironmentPostProcessors(ResourceLoader resourceLoader,
			ConfigurableBootstrapContext bootstrapContext) {
		...
		EnvironmentPostProcessorsFactory postProcessorsFactory = this.postProcessorsFactory.apply(classLoader);
		return postProcessorsFactory.getEnvironmentPostProcessors(this.deferredLogs, bootstrapContext);
	}
}
public interface EnvironmentPostProcessorsFactory {
	static EnvironmentPostProcessorsFactory fromSpringFactories(ClassLoader classLoader) {
		return new ReflectionEnvironmentPostProcessorsFactory(classLoader,
				SpringFactoriesLoader.loadFactoryNames(EnvironmentPostProcessor.class, classLoader));
	}
}
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor,\
org.springframework.boot.env.RandomValuePropertySourceEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor,\
org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor

EnvironmentPostProcessor

EnvironmentPostProcessor是处理程序的配置的,不同的子类实现不同的配置,有处理json配置文件的、有处理yaml配置文件的、有替换配置文件中的环境变量的。

  • SpringBoot 2.4.0之前的版本,处理yaml配置文件的类是ConfigFileApplicationListener类
  • SpringBoot 2.4.0之后的版本,处理yaml配置文件的类是ConfigDataEnvironmentPostProcessor类

SpringBoot 2.4.0之前

ConfigFileApplicationListener类

ConfigFileApplicationListener虽然是一个ApplicationListener,但同时实现了EnvironmentPostProcessor接口

public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {

	// Note the order is from least to most specific (last one wins)
	private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/";

	@Override
	public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
		addPropertySources(environment, application.getResourceLoader());
	}
	
	protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
		RandomValuePropertySource.addToEnvironment(environment);
		new Loader(environment, resourceLoader).load();
	}

	private class Loader {
		void load() {
			FilteredPropertySource.apply(this.environment, DefaultPropertiesPropertySource.NAME, LOAD_FILTERED_PROPERTY,
					this::loadWithFilteredProperties);
		}
	}
}

ConfigFileApplicationListener类中间一系列的load方法过程很长,就不一一展示了。

PropertySourceLoader

最后会来到YamlPropertySourceLoader类的load方法,读取yaml文件的配置,并加入到environment中。

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
public class YamlPropertySourceLoader implements PropertySourceLoader {

	@Override
	public String[] getFileExtensions() {
		return new String[] { "yml", "yaml" };
	}
	
	@Override
	public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
		if (!ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", getClass().getClassLoader())) {
			throw new IllegalStateException(
					"Attempted to load " + name + " but snakeyaml was not found on the classpath");
		}
		List<Map<String, Object>> loaded = new OriginTrackedYamlLoader(resource).load();
		if (loaded.isEmpty()) {
			return Collections.emptyList();
		}
		List<PropertySource<?>> propertySources = new ArrayList<>(loaded.size());
		for (int i = 0; i < loaded.size(); i++) {
			String documentNumber = (loaded.size() != 1) ? " (document #" + i + ")" : "";
			propertySources.add(new OriginTrackedMapPropertySource(name + documentNumber,
					Collections.unmodifiableMap(loaded.get(i)), true));
		}
		return propertySources;
	}
}

SpringBoot 2.4.0之后

ConfigDataEnvironmentPostProcessor

SpringBoot 2.4.0之后的版本,使用ConfigDataEnvironmentPostProcessor 类来加载yaml文件中的配置。

public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
	@Override
	public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
		postProcessEnvironment(environment, application.getResourceLoader(), application.getAdditionalProfiles());
	}

	void postProcessEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
			Collection<String> additionalProfiles) {
		...
		getConfigDataEnvironment(environment, resourceLoader, additionalProfiles).processAndApply();
		...
	}

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

ConfigDataEnvironment

ConfigDataEnvironmentPostProcessor 在postProcessEnvironment方法中会实例化一个ConfigDataEnvironment对象,这个对象会加载optional:classpath:/;optional:classpath:/config/;optional:file:./;optional:file:./config/;optional:file:./config/*/路径下的yaml文件

class ConfigDataEnvironment {
	static final String LOCATION_PROPERTY = "spring.config.location";
	static final String ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location";
	static final String IMPORT_PROPERTY = "spring.config.import";
	
	static final ConfigDataLocation[] DEFAULT_SEARCH_LOCATIONS;
	static {
		List<ConfigDataLocation> locations = new ArrayList<>();
		locations.add(ConfigDataLocation.of("optional:classpath:/;optional:classpath:/config/"));
		locations.add(ConfigDataLocation.of("optional:file:./;optional:file:./config/;optional:file:./config/*/"));
		DEFAULT_SEARCH_LOCATIONS = locations.toArray(new ConfigDataLocation[0]);
	}

	private ConfigDataEnvironmentContributors createContributors(Binder binder) {
		...
		contributors.addAll(getInitialImportContributors(binder));
		...
		return createContributors(contributors);
	}

	protected ConfigDataEnvironmentContributors createContributors(
			List<ConfigDataEnvironmentContributor> contributors) {
		return new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, contributors);
	}

	private List<ConfigDataEnvironmentContributor> getInitialImportContributors(Binder binder) {
		List<ConfigDataEnvironmentContributor> initialContributors = new ArrayList<>();
		addInitialImportContributors(initialContributors, bindLocations(binder, IMPORT_PROPERTY, EMPTY_LOCATIONS));
		addInitialImportContributors(initialContributors,
				bindLocations(binder, ADDITIONAL_LOCATION_PROPERTY, EMPTY_LOCATIONS));
		addInitialImportContributors(initialContributors,
				bindLocations(binder, LOCATION_PROPERTY, DEFAULT_SEARCH_LOCATIONS));
		return initialContributors;
	}
}
# ConfigData Location Resolvers
org.springframework.boot.context.config.ConfigDataLocationResolver=\
org.springframework.boot.context.config.ConfigTreeConfigDataLocationResolver,\
org.springframework.boot.context.config.StandardConfigDataLocationResolver

# ConfigData Loaders
org.springframework.boot.context.config.ConfigDataLoader=\
org.springframework.boot.context.config.ConfigTreeConfigDataLoader,\
org.springframework.boot.context.config.StandardConfigDataLoader
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
public class YamlPropertySourceLoader implements PropertySourceLoader {

	@Override
	public String[] getFileExtensions() {
		return new String[] { "yml", "yaml" };
	}

	@Override
	public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
		if (!ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", getClass().getClassLoader())) {
			throw new IllegalStateException(
					"Attempted to load " + name + " but snakeyaml was not found on the classpath");
		}
		List<Map<String, Object>> loaded = new OriginTrackedYamlLoader(resource).load();
		if (loaded.isEmpty()) {
			return Collections.emptyList();
		}
		List<PropertySource<?>> propertySources = new ArrayList<>(loaded.size());
		for (int i = 0; i < loaded.size(); i++) {
			String documentNumber = (loaded.size() != 1) ? " (document #" + i + ")" : "";
			propertySources.add(new OriginTrackedMapPropertySource(name + documentNumber,
					Collections.unmodifiableMap(loaded.get(i)), true));
		}
		return propertySources;
	}

}

方法调用很多,我只把关键的代码贴出来了。

List<PropertySource<?>>

中间有些存放到ConfigData,再到ConfigDataEnvironmentContributor,最后就是把PropertySource给environment变量。

org.springframework.boot.env.EnvironmentPostProcessor

如果想处理加载的配置文件,可以在自己的java项目里添加spring.factories,然后实现EnvironmentPostProcessor类。

org.springframework.boot.env.EnvironmentPostProcessor=\
com.camellibby.springboot.component.DemoEnvironmentPostProcessor
public class DemoEnvironmentPostProcessor implements EnvironmentPostProcessor {
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        System.out.println("DemoEnvironmentPostProcessor is starting");
    }
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 如果Spring Boot无法加载application.yml文件,可能是由于以下问题: 1. 文件命名错误:请确保application.yml文件的名称拼写正确,并且它在正确的位置。默认情况下,它应该位于src/main/resources目录下。 2. 文件编码问题:检查application.yml文件的编码格式是否正确。它应该是UTF-8编码,如果不是,尝试将其转换为UTF-8格式。 3. 语法错误:请检查application.yml文件的语法错误,例如缩进、格式错误或无效的配置项。可以尝试逐一注释掉配置项,然后逐渐取消注释以确定问题所在。 4. Maven或Gradle依赖问题:确保您的项目包含正确的Spring Boot依赖。可以通过在pom.xml(或build.gradle)文件添加以下依赖项来使用Spring Boot配置文件功能: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> ``` 5. Spring Boot配置问题:检查您的配置是否正确。例如,确认您是否正确配置spring.application.name和server.port等属性。 如果上述方法都无法解决问题,可以尝试重新构建项目,或者尝试将application.yml文件复制到其他位置进行测试。如果问题仍然存在,请检查您的开发环境是否正确配置,并尝试使用其他编辑器或IDE重新创建项目。 ### 回答2: Spring Boot 默认会自动加载 `application.yml` 文件,但有时候可能会出现无法加载的情况。如果遇到无法加载 `application.yml` 的问题,可能由以下几个原因导致: 1. 文件路径不正确:确保 `application.yml` 文件位于正确的路径下。在 Spring Boot ,`application.yml` 文件通常位于 `src/main/resources/` 目录下。 2. 文件名不正确:确保文件名是 `application.yml`,而不是其他命名形式如 `application.yaml` 或 `application.properties`。Spring Boot 默认使用 `.yml` 文件格式作为配置文件。 3. 文件格式错误:确保 `application.yml` 文件的格式正确。`.yml` 文件是使用 YAML 格式编写的,而不是其他格式如 JSON 或 XML。YAML 格式对缩进和空格等要求较严格,需要注意格式的正确性。 4. 依赖问题:检查项目的依赖是否正确配置。如果缺少相关依赖,可能会导致无法加载 `application.yml`。可以通过 Maven 或 Gradle 等构建工具来管理项目的依赖。 5. 配置文件读取问题:可能是由于代码读取配置文件的方式不正确导致。在 Spring Boot ,可以通过 `@ConfigurationProperties` 注解或 `@Value` 注解来读取 `application.yml` 配置信息。 如果上述方法仍然无法解决问题,可以尝试清理并重新构建项目,或者查看控制台是否有相关的错误或警告信息,以便更准确地判断问题所在。 ### 回答3: Spring Boot没有加载application.yml的原因可能有很多,我将列举几种常见情况: 1. yml文件位置不正确:Spring Boot默认会加载src/main/resources/application.yml文件,如果yml文件放在其他地方或文件名不正确,就无法加载配置信息。 2. 语法错误:yml文件使用的是YAML语言格式,如果文件存在语法错误,例如缩进不正确、冒号(:)后面缺失空格等,也会导致无法加载。 3. 依赖缺失:如果项目的依赖配置不正确,可能无法正确加载yml文件。确保在pom.xml引入了正确的Spring Boot依赖,例如spring-boot-starter-web等。 4. 编码问题:yml文件需要使用UTF-8编码来保存,如果编码格式不正确,也可能导致加载失败。 5. 配置属性名称错误:确保application.yml配置属性名称和代码的属性名称一致,包括大小写和特殊字符。 解决这个问题的方法是,首先确保yml文件位置、语法、编码等都正确,确认依赖配置也没问题。可以尝试重启项目,清理编译环境,重新构建项目。如果问题仍然存在,可以尝试使用其他方式加载配置,例如使用@PropertySource注解指定特定的配置文件路径或使用@Value注入属性值。另外,可以在启动类上添加@EnableAutoConfiguration注解,确保自动配置生效。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值