SpringBoot配置文件读取过程分析(springboot怎么获取配置文件的数据)

  本篇文章为你整理了SpringBoot配置文件读取过程分析(springboot怎么获取配置文件的数据)的详细内容,包含有springboot读取配置文件中文乱码 springboot怎么获取配置文件的数据 springboot配置文件怎么读取 springboot读取配置文件原理 SpringBoot配置文件读取过程分析,希望能帮助你了解 SpringBoot配置文件读取过程分析。

  整体流程分析

  SpringBoot的配置文件有两种 ,一种是 properties文件,一种是yml文件。在SpringBoot启动过程中会对这些文件进行解析加载。在SpringBoot启动的过程中,配置文件查找和解析的逻辑在listeners.environmentPrepared(environment)方法中。

  

void environmentPrepared(ConfigurableEnvironment environment) {

 

   for (SpringApplicationRunListener listener : this.listeners) {

   listener.environmentPrepared(environment);

  

 

  依次遍历监听器管理器的environmentPrepared方法,默认只有一个 EventPublishingRunListener 监听器管理器,代码如下,

  

@Override

 

  public void environmentPrepared(ConfigurableEnvironment environment) {

   this.initialMulticaster

   .multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));

  

 

  监听管理器的多播器有中有11个,其中针对配置文件的监听器类为 ConfigFileApplicationListener,会执行该类的 onApplicationEvent方法。代码如下,

  

@Override

 

  public void onApplicationEvent(ApplicationEvent event) {

   if (event instanceof ApplicationEnvironmentPreparedEvent) {

   // 执行 ApplicationEnvironmentPreparedEvent 事件

   onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);

   if (event instanceof ApplicationPreparedEvent) {

   onApplicationPreparedEvent(event);

  

 

  onApplicationEnvironmentPreparedEvent的逻辑为:先从/META-INF/spring.factories文件中获取实现了EnvironmentPostProcessor接口的环境变量后置处理器集合,再把当前的 ConfigFileApplicationListener 监听器添加到 环境变量后置处理器集合中(ConfigFileApplicationListener实现了EnvironmentPostProcessor接口),然后循环遍历 postProcessEnvironment 方法,并传入 事件的SpringApplication 对象和 环境变量。

  

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {

 

   List EnvironmentPostProcessor postProcessors = loadPostProcessors();

   postProcessors.add(this);

   AnnotationAwareOrderComparator.sort(postProcessors);

   for (EnvironmentPostProcessor postProcessor : postProcessors) {

   postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());

  

 

  在ConfigFileApplicationListener 的postProcessEnvironment方法中(其他几个环境变量后置处理器与读取配置文件无关),核心是创建了一个Load对象,并且调用了load()方法。

  

protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {

 

   RandomValuePropertySource.addToEnvironment(environment);

   new Loader(environment, resourceLoader).load();

  

 

  Loader是ConfigFileApplicationListener 的一个内部类,在Loader的构造方法中,会生成具体属性文件的资源加载类并赋值给this.propertySourceLoaders。代码如下,

  

Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {

 

   this.environment = environment;

   this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);

   this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();

   //从 /META-INF/spring.factories 中加载 PropertySourceLoader 的实现类

   this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,

   getClass().getClassLoader());

  

 

  从 /META-INF/spring.factories 中加载 PropertySourceLoader 的实现类,在具体解析资源文件的时候用到。具体的实现类如下,

  

org.springframework.boot.env.PropertySourceLoader=\

 

  org.springframework.boot.env.PropertiesPropertySourceLoader,\

  org.springframework.boot.env.YamlPropertySourceLoader

  

 

  Loader类的load方法是加载配置文件的入口方法,代码如下,

  

void load() {

 

   FilteredPropertySource.apply(...)

  

 

  FilteredPropertySource.apply()方法先判断是否存在以 defaultProperties 为名的 PropertySource 属性对象,如果不存在则执行operation.accept,如果存在则先替换,再执行operation.accept方法。代码如下:

  

static void apply(ConfigurableEnvironment environment, String propertySourceName, Set String filteredProperties,

 

   Consumer PropertySource ? operation) {

   // 在环境变量中获取 属性资源管理对象

   MutablePropertySources propertySources = environment.getPropertySources();

   // 根据 资源名称 获取属性资源对象

   PropertySource ? original = propertySources.get(propertySourceName);

   // 如果为null,则执行 operation.accept

   if (original == null) {

   operation.accept(null);

   return;

   //根据propertySourceName名称进行替换

   propertySources.replace(propertySourceName, new FilteredPropertySource(original, filteredProperties));

   try {

   operation.accept(original);

   finally {

   propertySources.replace(propertySourceName, original);

  

 

  operation.accept是一个函数接口,配置文件的解析和处理都在该方法中。具体的逻辑为:先初始化待处理的属性文件,再遍历解析待处理的属性文件并解析结果放在this.loaded中,然后添加this.loaded的数据至环境变量 this.environment.getPropertySources() 中,最后设置环境变量的ActiveProfiles属性。代码如下,

  

// 待处理的属性文件

 

  this.profiles = new LinkedList ();

  // 已处理的属性文件

  this.processedProfiles = new LinkedList ();

  this.activatedProfiles = false;

  // 已经加载的 属性文件和属性

  this.loaded = new LinkedHashMap ();

  // 添加 this.profiles 的 null 和 默认的属性文件

  initializeProfiles();

  // 循环 this.profiles 加载

  while (!this.profiles.isEmpty()) {

   Profile profile = this.profiles.poll();

   // 如果是主属性文件则先添加到环境变量中的 addActiveProfile

   if (isDefaultProfile(profile)) {

   addProfileToEnvironment(profile.getName());

   // 真正加载逻辑

   load(profile, this::getPositiveProfileFilter,

   addToLoaded(MutablePropertySources::addLast, false));

   this.processedProfiles.add(profile);

  // 加载 profile 为null 的

  load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));

  // 添加已经加载的 属性文件和属性至 环境变量 this.environment.getPropertySources() 中

  addLoadedPropertySources();

  //根据已处理的属性文件设置环境变量的ActiveProfiles

  applyActiveProfiles(defaultProperties);

  

 

  配置文件解析过程

  如上的load()的逻辑为:先获取所有的查找路径,再遍历查找路径并且获取属性配置文件的名称,最后根据名称和路径进行加载。这里会在 file:./config/,file:./,classpath:/config/,classpath:/ 四个不同的目录进行查找。优先级从左至右。

  

private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {

 

   // 获取 默认的 classpath:/,classpath:/config/,file:./,file:./config/ 文件路径

   // 根据路径查找具体的属性配置文件

   getSearchLocations().forEach((location) - {

   // 判断是否为文件夹

   boolean isFolder = location.endsWith("/");

   // 获取 属性配置文件的名称

   Set String names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;

   // 根据名称遍历进行加载具体路径下的具体的属性文件名

   names.forEach((name) - load(location, name, profile, filterFactory, consumer));

  

 

  getSearchLocations()获取属性文件搜索路径,如果环境变量中包括了 spring.config.location 则使用环境变量中配置的值,如果没有则使用默认的 file:./config/,file:./,classpath:/config/,classpath:/文件路径。代码如下,

  

// 获取搜索路径

 

  private Set String getSearchLocations() {

   // 如果环境变量中包括了 spring.config.location 则使用 环境变量配置的值。

   if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {

   return getSearchLocations(CONFIG_LOCATION_PROPERTY);

   // 获取 环境变量 spring.config.additional-location 的值

   Set String locations = getSearchLocations(CONFIG_ADDITIONAL_LOCATION_PROPERTY);

   // 添加默认的 classpath:/,classpath:/config/,file:./,file:./config/ 搜索文件

   // 倒叙排列后 为 file:./config/,file:./,classpath:/config/,classpath:/

   locations.addAll(

   asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS));

   return locations;

  

 

  getSearchNames()获取属性文件搜索名称,如果环境变量中有设置 spring.config.name 属性,则获取设置的名称,如果没有设置配置文件名称的环境变量则返回名称为 application。代码如下,

  

private Set String getSearchNames() {

 

   // 如果 环境变量中有设置 spring.config.name 属性,则获取设置的 名称

   if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {

   String property = this.environment.getProperty(CONFIG_NAME_PROPERTY);

   return asResolvedSet(property, null);

   // 如果没有设置环境变量 则返回名称为 application

   return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);

  

 

  names.forEach((name) - load(location, name, profile, filterFactory, consumer))中的load() 的逻辑为:先判断文件名name是否为null,如果为null则通过遍历属性资源加载器并且根据location进行加载属性资源文件;如果不为null ,则通过遍历属性资源加载器和遍历属性资源加载器的扩展名,根据location和 name 来加载属性资源文件,从配置文件可知,先会遍历执行 PropertiesPropertySourceLoader 的扩展名 ,然后遍历执行YamlPropertySourceLoader的扩展名 。代码如下,

  

private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,

 

   DocumentConsumer consumer) {

   // 如果文件名称为null

   if (!StringUtils.hasText(name)) {

   // 遍历属性资源加载器

   for (PropertySourceLoader loader : this.propertySourceLoaders) {

   // 根据属性资源加载的扩展名称进行过滤

   if (canLoadFileExtension(loader, location)) {

   load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);

   return;

   throw new IllegalStateException("File extension of config file location " + location

   + " is not known to any PropertySourceLoader. If the location is meant to reference "

   + "a directory, it must end in /");

   Set String processed = new HashSet ();

   // 遍历属性资源加载器

   for (PropertySourceLoader loader : this.propertySourceLoaders) {

   // 遍历属性资源加载器的扩展名

   for (String fileExtension : loader.getFileExtensions()) {

   if (processed.add(fileExtension)) {

   // 传入具体的属性文件路径和后缀名,进行加载属性资源文件

   loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,

   consumer);

  

 

   PropertiesPropertySourceLoader的扩展名包括:"properties", "xml" ;

   YamlPropertySourceLoader的扩展名包括:"yml", "yaml" 。

  


private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension,

 

   Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {

   DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);

   DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);

   if (profile != null) {

   // Try profile-specific file profile section in profile file (gh-340)

   String profileSpecificFile = prefix + "-" + profile + fileExtension;

   load(loader, profileSpecificFile, profile, defaultFilter, consumer);

   load(loader, profileSpecificFile, profile, profileFilter, consumer);

   // Try profile specific sections in files weve already processed

   for (Profile processedProfile : this.processedProfiles) {

   if (processedProfile != null) {

   String previouslyLoaded = prefix + "-" + processedProfile + fileExtension;

   load(loader, previouslyLoaded, profile, profileFilter, consumer);

   // Also try the profile-specific section (if any) of the normal file

   // 拼接文件路径和后缀名后,进行加载属性资源文件

   load(loader, prefix + fileExtension, profile, profileFilter, consumer);

  

 

  如上代码的load()方法主要逻辑为:先根据传入的文件路径生成 Resource 对象,如果该Resource 对象存在则解析成具体的documents对象,然后根据DocumentFilter 过滤器进行匹配,匹配成功则添加到 loaded 中,再进行倒叙排列。最后遍历 loaded 对象,调用consumer.accept ,将 profile 和 document 添加至 this.loaded 对象。

  

private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,

 

   DocumentConsumer consumer) {

   try {

   // 根据文件路径 获取资源

   Resource resource = this.resourceLoader.getResource(location);

   // 如果为 null 则返回

   if (resource == null !resource.exists()) {

   if (this.logger.isTraceEnabled()) {

   StringBuilder description = getDescription("Skipped missing config ", location, resource,

   profile);

   this.logger.trace(description);

   return;

   if (!StringUtils.hasText(StringUtils.getFilenameExtension(resource.getFilename()))) {

   if (this.logger.isTraceEnabled()) {

   StringBuilder description = getDescription("Skipped empty config extension ", location,

   resource, profile);

   this.logger.trace(description);

   return;

   String name = "applicationConfig: [" + location + "]";

   // 根据资源 和 属性资源解析器加载 List Document ,并进行缓存

   List Document documents = loadDocuments(loader, name, resource);

   if (CollectionUtils.isEmpty(documents)) {

   if (this.logger.isTraceEnabled()) {

   StringBuilder description = getDescription("Skipped unloaded config ", location, resource,

   profile);

   this.logger.trace(description);

   return;

   List Document loaded = new ArrayList ();

   // 遍历 documents

   for (Document document : documents) {

   // 如果匹配则添加

   if (filter.match(document)) {

   addActiveProfiles(document.getActiveProfiles());

   addIncludedProfiles(document.getIncludeProfiles());

   loaded.add(document);

   // 倒叙排列

   Collections.reverse(loaded);

   if (!loaded.isEmpty()) {

   //遍历 loaded 对象,调用consumer.accept ,将 profile 和 document 添加至 this.loaded 对象

   loaded.forEach((document) - consumer.accept(profile, document));

   if (this.logger.isDebugEnabled()) {

   StringBuilder description = getDescription("Loaded config file ", location, resource, profile);

   this.logger.debug(description);

   catch (Exception ex) {

   throw new IllegalStateException("Failed to load property source from location " + location + "", ex);

  

 

  至此配置文件解析全部处理完成,最终会把解析出来的配置文件和配置属性值添加到了 this.loaded 对象中。

  总结一下,默认情况下,属性配置文件的搜索路径为 file:./config/,file:./,classpath:/config/,classpath:/ ,优先级从左往右;配置文件名称为 application,扩展名为"properties", "xml","yml", "yaml",优先级从左往右。如果同一个配置属性配置在多个配置文件中,则取优先级最高的那个配置值。

  环境变量设置配置属性

  addLoadedPropertySources()方法,主要逻辑为:添加已经加载的属性文件添加至环境变量 this.environment 中 。代码如下,

  

private void addLoadedPropertySources() {

 

   // 获取环境变量的 PropertySources对象

   MutablePropertySources destination = this.environment.getPropertySources();

   List MutablePropertySources loaded = new ArrayList (this.loaded.values());

   // 倒序排列

   Collections.reverse(loaded);

   String lastAdded = null;

   Set String added = new HashSet ();

   for (MutablePropertySources sources : loaded) {

   for (PropertySource ? source : sources) {

   if (added.add(source.getName())) {

   // 添加 PropertySource 至 destination

   addLoadedPropertySource(destination, lastAdded, source);

   lastAdded = source.getName();

  

 

  applyActiveProfiles()主要逻辑为:根据已处理的属性文件设置环境变量environment的ActiveProfiles属性。

  

// 设置环境变量的ActiveProfiles

 

  private void applyActiveProfiles(PropertySource ? defaultProperties) {

   List String activeProfiles = new ArrayList ();

   if (defaultProperties != null) {

   Binder binder = new Binder(ConfigurationPropertySources.from(defaultProperties),

   new PropertySourcesPlaceholdersResolver(this.environment));

   activeProfiles.addAll(getDefaultProfiles(binder, "spring.profiles.include"));

   if (!this.activatedProfiles) {

   activeProfiles.addAll(getDefaultProfiles(binder, "spring.profiles.active"));

   this.processedProfiles.stream().filter(this::isDefaultProfile).map(Profile::getName)

   .forEach(activeProfiles::add);

   this.environment.setActiveProfiles(activeProfiles.toArray(new String[0]));

  

 

  以上就是SpringBoot配置文件读取过程分析(springboot怎么获取配置文件的数据)的详细内容,想要了解更多 SpringBoot配置文件读取过程分析的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: