Spring Boot application 文件加载

如何找到这个加载的过程

  1. 创建配置文件application.yml

  2. 全局搜索 yml

    image-20200319083048849

  3. 换成properties搜索

    image-20200319083140225

  4. 我们以yml为例打上断点开始源码追踪

看到调用堆栈

image-20200319083345067

  • 一步一步回上去看如何调用具体方法的

ConfigFileApplicationListener

  • 配置文件监听器

调用过程

image-20200319082131146

image-20200319082544653

org.springframework.boot.context.config.ConfigFileApplicationListener#addPropertySources

  1. protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
  2. RandomValuePropertySource.addToEnvironment(environment);
  3. // 加载器加载信息
  4. new Loader(environment, resourceLoader).load();
  5. }

Loader

  • 配置资源加载器

构造方法

  1. Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
  2. // 环境配置
  3. this.environment = environment;
  4. // 占位符处理器
  5. this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
  6. // 资源加载器
  7. this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
  8. // 配置信息加载器初始化
  9. this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
  10. getClass().getClassLoader());
  11. }
  • 熟悉的老朋友this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, getClass().getClassLoader()), 看看spring.factories有什么

    • 搜索目标: org.springframework.boot.env.PropertySourceLoader

      image-20200319084141748

image-20200319084151997

观察发现里面有一个YamlPropertySourceLoader和我们之前找 yml 字符串的时候找到的类是一样的。说明搜索方式没有什么问题。

image-20200319084357652

初始化完成,后续进行解析了

load 方法

  1. void load() {
  2. FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
  3. (defaultProperties) -> {
  4. this.profiles = new LinkedList<>();
  5. this.processedProfiles = new LinkedList<>();
  6. this.activatedProfiles = false;
  7. this.loaded = new LinkedHashMap<>();
  8. // 初始化配置文件
  9. initializeProfiles();
  10. while (!this.profiles.isEmpty()) {
  11. Profile profile = this.profiles.poll();
  12. if (isDefaultProfile(profile)) {
  13. addProfileToEnvironment(profile.getName());
  14. }
  15. load(profile, this::getPositiveProfileFilter,
  16. addToLoaded(MutablePropertySources::addLast, false));
  17. this.processedProfiles.add(profile);
  18. }
  19. load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
  20. addLoadedPropertySources();
  21. applyActiveProfiles(defaultProperties);
  22. });
  23. }

initializeProfiles

  • 初始化private Deque<Profile> profiles; 属性
  • image-20200319084902957

load

  • org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#load(org.springframework.boot.context.config.ConfigFileApplicationListener.Profile, org.springframework.boot.context.config.ConfigFileApplicationListener.DocumentFilterFactory, org.springframework.boot.context.config.ConfigFileApplicationListener.DocumentConsumer)
  1. private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
  2. getSearchLocations().forEach(
  3. // 本地路径
  4. (location) -> {
  5. // 是不是文件夹
  6. boolean isFolder = location.endsWith("/");
  7. // 文件名,默认application
  8. Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;
  9. // 循环加载
  10. names.forEach((name) -> {
  11. load(location, name, profile, filterFactory, consumer);
  12. });
  13. });
  14. }
  • 资源路径可能性

image-20200319085446640

该方法采用循环每个路径下面都去尝试一遍

  • 中间过程省略,我们直接看最后的加载行为
    • org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#loadDocuments
  1. private List<Document> loadDocuments(PropertySourceLoader loader, String name, Resource resource)
  2. throws IOException {
  3. // 文档的缓存key
  4. DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource);
  5. // 文档信息
  6. List<Document> documents = this.loadDocumentsCache.get(cacheKey);
  7. if (documents == null) {
  8. // 执行加载,将配置文件读取返回
  9. List<PropertySource<?>> loaded = loader.load(name, resource);
  10. // 数据转换
  11. documents = asDocuments(loaded);
  12. // 缓存设置
  13. this.loadDocumentsCache.put(cacheKey, documents);
  14. }
  15. return documents;
  16. }

此处的loader.load()调用具体的 loader 实现类进行执行方法

yml 解析

  1. @Override
  2. public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
  3. if (!ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) {
  4. throw new IllegalStateException(
  5. "Attempted to load " + name + " but snakeyaml was not found on the classpath");
  6. }
  7. // 将资源转换成集合对象
  8. List<Map<String, Object>> loaded = new OriginTrackedYamlLoader(resource).load();
  9. if (loaded.isEmpty()) {
  10. return Collections.emptyList();
  11. }
  12. List<PropertySource<?>> propertySources = new ArrayList<>(loaded.size());
  13. for (int i = 0; i < loaded.size(); i++) {
  14. String documentNumber = (loaded.size() != 1) ? " (document #" + i + ")" : "";
  15. // 放入返回结果中
  16. propertySources.add(new OriginTrackedMapPropertySource(name + documentNumber,
  17. Collections.unmodifiableMap(loaded.get(i)), true));
  18. }
  19. return propertySources;
  20. }

image-20200319090446231

  • PropertiesPropertySourceLoader解析同理不在次展开描述了

asDocuments

  1. /**
  2. * 将 {@link PropertySource} 转换成 {@link Document}
  3. * @param loaded
  4. * @return
  5. */
  6. private List<Document> asDocuments(List<PropertySource<?>> loaded) {
  7. if (loaded == null) {
  8. return Collections.emptyList();
  9. }
  10. return loaded.stream().map(
  11. // 循环创建新对象
  12. (propertySource) -> {
  13. // 对象创建
  14. Binder binder = new Binder(ConfigurationPropertySources.from(propertySource),
  15. this.placeholdersResolver);
  16. /**
  17. * 通过 {@link Binder} 将数据进行绑定,创建 {@link Document}进行返回
  18. */
  19. return new Document(propertySource, binder.bind("spring.profiles", STRING_ARRAY).orElse(null),
  20. getProfiles(binder, ACTIVE_PROFILES_PROPERTY),
  21. getProfiles(binder, INCLUDE_PROFILES_PROPERTY));
  22. }).collect(Collectors.toList());
  23. }