# Spring Boot application 文件加载 - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [SourceHot-spring-boot](https://github.com/SourceHot/spring-boot-read) ## 如何找到这个加载的过程 1. 创建配置文件`application.yml` 2. 全局搜索yml ![image-20200319083048849](/images/SpringBoot/image-20200319083048849.png) 3. 换成`properties`搜索 ![image-20200319083140225](/images/SpringBoot/image-20200319083140225.png) 4. 我们以`yml`为例打上断点开始源码追踪 看到调用堆栈 ![image-20200319083345067](/images/SpringBoot/image-20200319083345067.png) - 一步一步回上去看如何调用具体方法的 ## ConfigFileApplicationListener - 配置文件监听器 ### 调用过程 ![image-20200319082131146](/images/SpringBoot/image-20200319082131146.png) ![image-20200319082544653](/images/SpringBoot/image-20200319082544653.png) `org.springframework.boot.context.config.ConfigFileApplicationListener#addPropertySources` ```java protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { RandomValuePropertySource.addToEnvironment(environment); // 加载器加载信息 new Loader(environment, resourceLoader).load(); } ``` ### Loader - 配置资源加载器 构造方法 ```java Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { // 环境配置 this.environment = environment; // 占位符处理器 this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment); // 资源加载器 this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader(); // 配置信息加载器初始化 this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, getClass().getClassLoader()); } ``` - 熟悉的老朋友`this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, getClass().getClassLoader())`, 看看**`spring.factories`**有什么 - 搜索目标: `org.springframework.boot.env.PropertySourceLoader` ![image-20200319084141748](/images/SpringBoot/image-20200319084141748.png) ![image-20200319084151997](/images/SpringBoot/image-20200319084151997.png) 观察发现里面有一个`YamlPropertySourceLoader`和我们之前找yml字符串的时候找到的类是一样的。说明搜索方式没有什么问题。 ![image-20200319084357652](/images/SpringBoot/image-20200319084357652.png) 初始化完成,后续进行解析了 ### load 方法 ```java 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()); } load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false)); this.processedProfiles.add(profile); } load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true)); addLoadedPropertySources(); applyActiveProfiles(defaultProperties); }); } ``` ### initializeProfiles - 初始化`private Deque profiles;` 属性 - ![image-20200319084902957](/images/SpringBoot/image-20200319084902957.png) ### 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)` ```JAVA private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) { getSearchLocations().forEach( // 本地路径 (location) -> { // 是不是文件夹 boolean isFolder = location.endsWith("/"); // 文件名,默认application Set names = isFolder ? getSearchNames() : NO_SEARCH_NAMES; // 循环加载 names.forEach((name) -> { load(location, name, profile, filterFactory, consumer); }); }); } ``` - 资源路径可能性 ![image-20200319085446640](/images/SpringBoot/image-20200319085446640.png) 该方法采用循环每个路径下面都去尝试一遍 - 中间过程省略,我们直接看最后的加载行为 - `org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#loadDocuments` ```java private List loadDocuments(PropertySourceLoader loader, String name, Resource resource) throws IOException { // 文档的缓存key DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource); // 文档信息 List documents = this.loadDocumentsCache.get(cacheKey); if (documents == null) { // 执行加载,将配置文件读取返回 List> loaded = loader.load(name, resource); // 数据转换 documents = asDocuments(loaded); // 缓存设置 this.loadDocumentsCache.put(cacheKey, documents); } return documents; } ``` 此处的`loader.load()`调用具体的loader实现类进行执行方法 ### yml 解析 ```java @Override public List> load(String name, Resource resource) throws IOException { if (!ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) { throw new IllegalStateException( "Attempted to load " + name + " but snakeyaml was not found on the classpath"); } // 将资源转换成集合对象 List> loaded = new OriginTrackedYamlLoader(resource).load(); if (loaded.isEmpty()) { return Collections.emptyList(); } List> 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; } ``` ![image-20200319090446231](/images/SpringBoot/image-20200319090446231.png) - `PropertiesPropertySourceLoader`解析同理不在次展开描述了 ### asDocuments ```JAVA /** * 将 {@link PropertySource} 转换成 {@link Document} * @param loaded * @return */ private List asDocuments(List> loaded) { if (loaded == null) { return Collections.emptyList(); } return loaded.stream().map( // 循环创建新对象 (propertySource) -> { // 对象创建 Binder binder = new Binder(ConfigurationPropertySources.from(propertySource), this.placeholdersResolver); /** * 通过 {@link Binder} 将数据进行绑定,创建 {@link Document}进行返回 */ return new Document(propertySource, binder.bind("spring.profiles", STRING_ARRAY).orElse(null), getProfiles(binder, ACTIVE_PROFILES_PROPERTY), getProfiles(binder, INCLUDE_PROFILES_PROPERTY)); }).collect(Collectors.toList()); } ``` -----