文章目录

  • 前言
  • 结论
  • 1. 什么时候读取完配置文件
  • 2. 配置文件的读取顺序
  • 1. 目录读取顺序
  • 2. 文件类型读取顺序
  • 3. 文件名读取顺序
  • 3. 配置打印类
  • 源码分析
  • 1. 文件名顺序源码
  • 2. 文件目录读取顺序源码getSearchLocations
  • 3. 配置文件的读取顺序


前言

以前有时遇到找不到相关配置,于是想知道当前服务读取到哪些配置。从源码分析一下。

本文spring boot版本为:2.3.7.RELEASE

结论

1. 什么时候读取完配置文件

main函数从SpringApplication.run方法进入,执行完prepareEnvironment就获取到了本地的配置文件

SpringApplication.java

SpringApplication.run
->ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
->listeners.environmentPrepared(environment);

springboot Scheduled 串行执行 springboot执行顺序_spring

执行完listeners.environmentPrepared(environment);后就可以到

environment.propertySources.propertySourceList的结尾找对应的环境信息

springboot Scheduled 串行执行 springboot执行顺序_java_02

2. 配置文件的读取顺序

1. 目录读取顺序

  • jar包所在目录下的config目录 file:./config/
  • jar包所在目录下的config目录下的第二层目录 file:./config/*/
  • jar包所在目录 file:./
  • jar包内classes/config的目录(对应项目中的resources/config目录) classpath:/config/
  • jar包内classess的目录(对应项目中的resources目录) classpath:/

2. 文件类型读取顺序

  • .properties
  • .xml
  • .yml
  • .yaml

3. 文件名读取顺序

一般项目只会选择一种文件类型作为配置文件,如yml,但配置spring.profiles.active后,会按照配置的名称顺序进行读取。

如在application.yml配置 spring.profiles.active: minio

则读取的文件顺序为:

  1. application.yml
  2. application-minio.(properties/xml/yml/yaml)

3. 配置打印类

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;

import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

/**
 * <p> Description:打印配置文件信息</p>
 * <p> CreationTime: 2022/10/31 19:03
 *
 * @author dreambyday
 * @since 1.0
 */
@Slf4j
@Component
public class ConfigFileLog implements EnvironmentAware {
    private final List<PropertySource<?>> allPropertySource = new ArrayList<>();

    @Override
    public void setEnvironment(Environment environment) {
        if (!allPropertySource.isEmpty()) {
            return ;
        }
        if (environment instanceof AbstractEnvironment) {
            MutablePropertySources propertySources = ((AbstractEnvironment) environment).getPropertySources();
            for (PropertySource<?> propertySource : propertySources) {
                if (propertySource instanceof OriginTrackedMapPropertySource) {
                    allPropertySource.add(propertySource);
                }
            }
        }
    }

    @PostConstruct
    private void show() {
        for (PropertySource<?> propertySource : allPropertySource) {
            log.info("--------------打印配置文件------------{}", propertySource.getName());
            Object source = propertySource.getSource();
            if (source instanceof Map) {
                for (Map.Entry<?, ?> entry : ((Map<?, ?>) source).entrySet()) {
                    log.info(entry.getKey() + ":" + entry.getValue());
                }
            }
            log.info("--------------完成------------------");
        }
    }
}

源码分析

以配置文件为resource/application.yml 和 resource/application-minio.yml为例

SpringApplication.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);
       // 这一步获取到配置文件中的配置
      ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
      configureIgnoreBeanInfo(environment);
      Banner printedBanner = printBanner(environment);
      context = createApplicationContext();
      exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);
      prepareContext(context, environment, listeners, applicationArguments, printedBanner);
      refreshContext(context);
      afterRefresh(context, applicationArguments);
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
      }
      listeners.started(context);
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, listeners);
      throw new IllegalStateException(ex);
   }

   try {
      listeners.running(context);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, null);
      throw new IllegalStateException(ex);
   }
   return context;
}
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments) {
   // Create and configure the environment
   ConfigurableEnvironment environment = getOrCreateEnvironment();
   configureEnvironment(environment, applicationArguments.getSourceArgs());
   ConfigurationPropertySources.attach(environment);
    // 在这里读取配置文件。listeners的类型为EventPublishingRunListener 
   listeners.environmentPrepared(environment);
   bindToSpringApplication(environment);
   if (!this.isCustomEnvironment) {
      environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
            deduceEnvironmentClass());
   }
   ConfigurationPropertySources.attach(environment);
   return environment;
}

EventPublishingRunListener.java

@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
    this.initialMulticaster
        .multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}

SimpleApplicationEventMulticaster.java

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    Executor executor = getTaskExecutor();
    // ConfigFileApplicationListener  这个是读取配置文件的监听器
    // 其他监听器,跟读取配置无关
    // AnsiOutputApplicationListener、LoggingApplicationListener、ClasspathLoggingApplicationListener
    // BackgroundPreintializer、DelegatingApplicationListener、FileEncodingApplicationListener
    for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
        if (executor != null) {
            executor.execute(() -> invokeListener(listener, event));
        }
        else {
            // 从这里调用listener
            invokeListener(listener, event);
        }
    }
}
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
    ErrorHandler errorHandler = getErrorHandler();
    if (errorHandler != null) {
        try {
            doInvokeListener(listener, event);
        }
        catch (Throwable err) {
            errorHandler.handleError(err);
        }
    }
    else {
		// 这里进入
        doInvokeListener(listener, event);
    }
}

ConfigFileApplicationListener.java

@Override
public void onApplicationEvent(ApplicationEvent event) {
    if (event instanceof ApplicationEnvironmentPreparedEvent) {
        // 这里进入
        onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
    }
    if (event instanceof ApplicationPreparedEvent) {
        onApplicationPreparedEvent(event);
    }
}
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
    List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
    postProcessors.add(this);
    AnnotationAwareOrderComparator.sort(postProcessors);
    // 读取文件的后置处理:COnfigFileApplicationListener
    for (EnvironmentPostProcessor postProcessor : postProcessors) {
        // 进入
        postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
    }
}

其他监听器

springboot Scheduled 串行执行 springboot执行顺序_配置文件_03

protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
    RandomValuePropertySource.addToEnvironment(environment);
    // load()方法进入
    new Loader(environment, resourceLoader).load();
}

1. 文件名顺序源码

void load() {
    FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
                                 (defaultProperties) -> {
                                     this.profiles = new LinkedList<>();
                                     this.processedProfiles = new LinkedList<>();
                                     this.activatedProfiles = false;
                                     this.loaded = new LinkedHashMap<>();
                                     initializeProfiles();
                                     while (!this.profiles.isEmpty()) {
                                         Profile profile = this.profiles.poll();
                                         if (isDefaultProfile(profile)) {
                                             addProfileToEnvironment(profile.getName());
                                         }
                                         // 这里进入。第一次读取默认配置文件application.?,在配置文件中找到spring.profiles.active指定文件,加入profiles队列,直到找完。
                                         load(profile, this::getPositiveProfileFilter,
                                              addToLoaded(MutablePropertySources::addLast, false));
                                         this.processedProfiles.add(profile);
                                     }
                                     load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
                                     addLoadedPropertySources();
                                     applyActiveProfiles(defaultProperties);
                                 });
}
private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
   getSearchLocations().forEach((location) -> {
      boolean isDirectory = location.endsWith("/");
      Set<String> names = isDirectory ? getSearchNames() : NO_SEARCH_NAMES;
       // 当location为classpath:/  时从这里进入
      names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
   });
}

2. 文件目录读取顺序源码getSearchLocations

private Set<String> getSearchLocations() {
   Set<String> locations = getSearchLocations(CONFIG_ADDITIONAL_LOCATION_PROPERTY);
   if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
      locations.addAll(getSearchLocations(CONFIG_LOCATION_PROPERTY));
   }
   else {
      locations.addAll(
            asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS));
   }
   return locations;
}

返回配置文件读取顺序locations

0 = "file:./config/"
1 = "file:./config/*/"
2 = "file:./"
3 = "classpath:/config/"
4 = "classpath:/"

3. 配置文件的读取顺序

先读取PropertiesPropertySourceLoader可以解析的配置文件类型:properties和xml。再读取YamlPropertySourceLoader解析yml和yaml

private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
      DocumentConsumer consumer) {
   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<>();
    // 两个解析类:PropertiesPropertySourceLoader(properties、xml) YamlPropertySourceLoader (yml、yaml)
   for (PropertySourceLoader loader : this.propertySourceLoaders) {
      for (String fileExtension : loader.getFileExtensions()) {
         if (processed.add(fileExtension)) {
             // 当loader为YamlPropertySourceLoader并且fileExtension为yml时 这里进入
            loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,
                  consumer);
         }
      }
   }
}

后面是再往下的源码了。

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 we've 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);
}
// location:classpath:/application.yml
private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,
				DocumentConsumer consumer) {
    		
			Resource[] resources = getResources(location);
			for (Resource resource : resources) {
				try {
					if (resource == null || !resource.exists()) {
						if (this.logger.isTraceEnabled()) {
							StringBuilder description = getDescription("Skipped missing config ", location, resource,
									profile);
							this.logger.trace(description);
						}
						continue;
					}
					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);
						}
						continue;
					}
					if (resource.isFile() && isPatternLocation(location) && hasHiddenPathElement(resource)) {
						if (this.logger.isTraceEnabled()) {
							StringBuilder description = getDescription("Skipped location with hidden path element ",
									location, resource, profile);
							this.logger.trace(description);
						}
						continue;
					}
					String name = "applicationConfig: [" + getLocationName(location, resource) + "]";
                    // 在这里读取到配置文件中的配置。第一次读取application.yml
					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);
						}
						continue;
					}
					List<Document> loaded = new ArrayList<>();
					for (Document document : documents) {
						if (filter.match(document)) {
							addActiveProfiles(document.getActiveProfiles());
							addIncludedProfiles(document.getIncludeProfiles());
							loaded.add(document);
						}
					}
					Collections.reverse(loaded);
					if (!loaded.isEmpty()) {
						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) {
					StringBuilder description = getDescription("Failed to load property source from ", location,
							resource, profile);
					throw new IllegalStateException(description.toString(), ex);
				}
			}
		}