spring boot实战(第六篇)加载application资源文件源码分析

前言

在上一篇中了解了spring配置资源的加载过程,本篇在此基础上学习spring boot如何默认加载application.xml等文件信息的。

 

 

ConfigFileApplicationListener

spring boot实战(第三篇)事件监听源码分析中可知在构造SpringApplication时加载相关的监听器,其中存在一个监听器ConfigFileApplicationListener,其定义如下:

[html] view plain copy

  1. public class ConfigFileApplicationListener implements  
  2.         ApplicationListener<ApplicationEvent>, Ordered {  
  3. @Override  
  4.     public void onApplicationEvent(ApplicationEvent event) {  
  5.         if (event instanceof ApplicationEnvironmentPreparedEvent) {  
  6.             onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);  
  7.         }  
  8.         if (event instanceof ApplicationPreparedEvent) {  
  9.             onApplicationPreparedEvent((ApplicationPreparedEvent) event);  
  10.         }  
  11.     }  
  12.    
  13. }  

监听ApplicationEvent事件,在触发所有其子类以及本身事件时会执行其onApplicationEvent方法。在执行

[html] view plain copy

  1. for (SpringApplicationRunListener runListener : runListeners) {  
  2.     runListener.environmentPrepared(environment);  
  3. }  

时会触发到

[html] view plain copy

  1. if (event instanceof ApplicationEnvironmentPreparedEvent) {  
  2.             onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);  
  3.         }  

中;

 

[html] view plain copy

  1. private void onApplicationEnvironmentPreparedEvent(  
  2.             ApplicationEnvironmentPreparedEvent event) {  
  3.         Environment environment = event.getEnvironment();  
  4.         if (environment instanceof ConfigurableEnvironment) {  
  5.             onApplicationEnvironmentPreparedEvent((ConfigurableEnvironment) environment,  
  6.                     event.getSpringApplication());  
  7.         }  
  8.     }  

 

在上一篇中可以知道enviroment为StandardServletEnvironment实例,因此执行onApplicationEnvironmentPreparedEvent方法

 

[html] view plain copy

  1. private void onApplicationEnvironmentPreparedEvent(  
  2.             ConfigurableEnvironment environment, SpringApplication application) {  
  3.         addPropertySources(environment, application.getResourceLoader());  
  4.         bindToSpringApplication(environment, application);  
  5.     }  

首先来看addPropertySources相关信息

 

[html] view plain copy

  1. protected void addPropertySources(ConfigurableEnvironment environment,  
  2.             ResourceLoader resourceLoader) {  
  3.         RandomValuePropertySource.addToEnvironment(environment);  
  4.         try {  
  5.             new Loader(environment, resourceLoader).load();  
  6.         }  
  7.         catch (IOException ex) {  
  8.             throw new IllegalStateException(“Unable to load configuration files”, ex);  
  9.         }  
  10.     }  

 

RandomValuePropertySource.addToEnvironment(environment)将随机方法放入到PropertySources中

 

[html] view plain copy

  1. public static void addToEnvironment(ConfigurableEnvironment environment) {  
  2.         environment.getPropertySources().addAfter(  
  3.                 StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,  
  4.                 new RandomValuePropertySource(“random”));  
  5.         logger.trace(“RandomValuePropertySource add to Environment”);  
  6.     }  

如何从Random中获取值是需要看getProperty方法:

[html] view plain copy

  1. public Object getProperty(String name) {  
  2.         if (!name.startsWith(“random.”)) {  
  3.             return null;  
  4.         }  
  5.         if (logger.isTraceEnabled()) {  
  6.             logger.trace(“Generating random property for ‘” + name + “‘”);  
  7.         }  
  8.         if (name.endsWith(“int”)) {  
  9.             return getSource().nextInt();  
  10.         }  
  11.         if (name.startsWith(“random.long”)) {  
  12.             return getSource().nextLong();  
  13.         }  
  14.         if (name.startsWith(“random.int”) && name.length() > “random.int”.length() + 1) {  
  15.             String range = name.substring(“random.int”.length() + 1);  
  16.             range = range.substring(0, range.length() – 1);  
  17.             return getNextInRange(range);  
  18.         }  
  19.         byte[] bytes = new byte[32];  
  20.         getSource().nextBytes(bytes);  
  21.         return DigestUtils.md5DigestAsHex(bytes);  
  22.     }  

其中的getSource()表示Random类。

接下来看

[html] view plain copy

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

看load方法

[html] view plain copy

  1. public void load() throws IOException {  
  2.             …//处理profiles信息  
  3.               
  4.             while (!this.profiles.isEmpty()) {  
  5.                 String profile = this.profiles.poll();  
  6.                 for (String location : getSearchLocations()) {  
  7.                     if (!location.endsWith(“/”)) {  
  8.                         // location is a filename already, so don’t search for more  
  9.                         // filenames  
  10.                         load(location, null, profile);  
  11.                     }  
  12.                     else {  
  13.                         for (String name : getSearchNames()) {  
  14.                             load(location, name, profile);  
  15.                         }  
  16.                     }  
  17.                 }  
  18.             }  
  19.   
  20.             addConfigurationProperties(this.propertiesLoader.getPropertySources());  
  21.         }  

看getSearchLocations()方法

 

[html] view plain copy

  1. private Set<String> getSearchLocations() {  
  2.             Set<String> locations = new LinkedHashSet<String>();  
  3.             // User-configured settings take precedence, so we do them first  
  4.             if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {  
  5.                 for (String path : asResolvedSet(  
  6.                         this.environment.getProperty(CONFIG_LOCATION_PROPERTY), null)) {  
  7.                     if (!path.contains(“$”)) {  
  8.                         if (!path.contains(“:”)) {  
  9.                             path = “file:” + path;  
  10.                         }  
  11.                         path = StringUtils.cleanPath(path);  
  12.                     }  
  13.                     locations.add(path);  
  14.                 }  
  15.             }  
  16.             locations.addAll(asResolvedSet(  
  17.                     ConfigFileApplicationListener.this.searchLocations,  
  18.                     DEFAULT_SEARCH_LOCATIONS));  
  19.             return locations;  
  20.         }  

首先看CONFIG_LOCATION_PROPERTY(spring.config.location)是否存在配置,无则走默认配置路径DEFAULT_SEARCH_LOCATIONS(classpath:/,classpath:/config/,file:./,file:./config/)

 

继续来看getSearchNames()方法

 

[html] view plain copy

  1. private Set<String> getSearchNames() {  
  2.         if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {  
  3.             return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY),  
  4.                     null);  
  5.         }  
  6.         return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);  
  7.     }  

优先看CONFIG_NAME_PROPERTY(spring.config.name)配置,否则走DEFAULT_NAMES(application)

 

解析完路径和配置文件名以后,将开始判断路径+名称组合是否存在  执行load(…)方法

[html] view plain copy

  1. private void load(String location, String name, String profile)  
  2.                 throws IOException {  
  3.             String group = “profile=” + (profile == null ? “” : profile);  
  4.             if (!StringUtils.hasText(name)) {  
  5.                 // Try to load directly from the location  
  6.                 loadIntoGroup(group, location, profile);  
  7.             }  
  8.             else {  
  9.                 // Search for a file with the given name  
  10.                 for (String ext : this.propertiesLoader.getAllFileExtensions()) {  
  11.                     if (profile != null) {  
  12.                         // Try the profile specific file  
  13.                         loadIntoGroup(group, location + name + “-” + profile + “.” + ext,  
  14.                                 null);  
  15.                         // Sometimes people put “spring.profiles: dev” in  
  16.                         // application-dev.yml (gh-340). Arguably we should try and error  
  17.                         // out on that, but we can be kind and load it anyway.  
  18.                         loadIntoGroup(group, location + name + “-” + profile + “.” + ext,  
  19.                                 profile);  
  20.                     }  
  21.                     // Also try the profile specific section (if any) of the normal file  
  22.                     loadIntoGroup(group, location + name + “.” + ext, profile);  
  23.                 }  
  24.             }  
  25.         }  

this.propertiesLoader.getAllFileExtensions()方法获取文件后缀

 

[html] view plain copy

  1. public Set<String> getAllFileExtensions() {  
  2.         Set<String> fileExtensions = new HashSet<String>();  
  3.         for (PropertySourceLoader loader : this.loaders) {  
  4.             fileExtensions.addAll(Arrays.asList(loader.getFileExtensions()));  
  5.         }  
  6.         return fileExtensions;  
  7.     }  

loader.getFileExtensions()获取所有支持的文件后缀,其中loader在执行load方法时实例化

 

[html] view plain copy

  1. public void load() throws IOException {  
  2.             this.propertiesLoader = new PropertySourcesLoader();  
  3. …}  

调用其构造方法

[html] view plain copy

  1. public PropertySourcesLoader(MutablePropertySources propertySources) {  
  2.     Assert.notNull(propertySources, “PropertySources must not be null”);  
  3.     this.propertySources = propertySources;  
  4.     this.loaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,  
  5.             null);  
  6. }  

可以看出this.loaders是由SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,null)得到

 

[html] view plain copy

  1. public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {  
  2.     Assert.notNull(factoryClass, “‘factoryClass’ must not be null”);  
  3.     ClassLoader classLoaderToUse = classLoader;  
  4.     if (classLoaderToUse == null) {  
  5.         classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();  
  6.     }  
  7.     List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);  
  8.     if (logger.isTraceEnabled()) {  
  9.         logger.trace(“Loaded [” + factoryClass.getName() + “] names: ” + factoryNames);  
  10.     }  
  11.     List<T> result = new ArrayList<T>(factoryNames.size());  
  12.     for (String factoryName : factoryNames) {  
  13.         result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));  
  14.     }  
  15.     AnnotationAwareOrderComparator.sort(result);  
  16.     return result;  
  17. }  

加载META-INF/spring.factories文件下对应内容

[html] view plain copy

  1. # PropertySource Loaders  
  2. org.springframework.boot.env.PropertySourceLoader=\  
  3. org.springframework.boot.env.PropertiesPropertySourceLoader,\  
  4. org.springframework.boot.env.YamlPropertySourceLoader  

 

因此加载了PropertiesPropertySourceLoader以及YamlPropertySourceLoader类实例;

  • PropertiesPropertySourceLoader 支持文件后缀格式 “properties”,”xml” 

[html] view plain copy

  1. @Override  
  2.     public String[] getFileExtensions() {  
  3.         return new String[] { “properties”, “xml” };  
  4.     }  
  • YamlPropertySourceLoader 支持文件后缀格式 “yml”,”yaml”

[html] view plain copy

  1. @Override  
  2.     public String[] getFileExtensions() {  
  3.         return new String[] { “yml”, “yaml” };  
  4.     }  

两者覆写的load方法实现如何处理资源为PropertySource对象。

 

获取完文件后缀后调用loadIntoGroup方法将资源信息转化为PropertySource,其实质为调用PropertySourcesLoader中load方法

[html] view plain copy

  1. private PropertySource<?> loadIntoGroup(String identifier, String location,  
  2.                 String profile) throws IOException {  
  3.             Resource resource = this.resourceLoader.getResource(location);  
  4.             PropertySource<?> propertySource = null;  
  5.             if (resource != null) {  
  6.                 String name = “applicationConfig: [“ + location + “]”;  
  7.                 String group = “applicationConfig: [“ + identifier + “]”;  
  8.                 propertySource = this.propertiesLoader.load(resource, group, name,  
  9.                         profile);  
  10.                 if (propertySource != null) {  
  11.                     maybeActivateProfiles(propertySource  
  12.                             .getProperty(ACTIVE_PROFILES_PROPERTY));  
  13.                     addIncludeProfiles(propertySource  
  14.                             .getProperty(INCLUDE_PROFILES_PROPERTY));  
  15.                 }  
  16.             }  
  17.             StringBuilder msg = new StringBuilder();  
  18.             msg.append(propertySource == null ? “Skipped ” : “Loaded “);  
  19.             msg.append(“config file “);  
  20.             msg.append(“‘”).append(location).append(“‘”);  
  21.             if (StringUtils.hasLength(profile)) {  
  22.                 msg.append(” for profile” + profile);  
  23.             }  
  24.             if (resource == null || !resource.exists()) {  
  25.                 msg.append(” resource not found”);  
  26.             }  
  27.             this.debug.add(msg);  
  28.             return propertySource;  
  29.         }  

最后调用addConfigurationProperties(this.propertiesLoader.getPropertySources())方法将解析过后的资源信息放置进Enviroment中propertySources属性集合中

[html] view plain copy

  1. private void addConfigurationProperties(MutablePropertySources sources) {  
  2.             List<PropertySource<?>> reorderedSources = new ArrayList<PropertySource<?>>();  
  3.             for (PropertySource<?> item : sources) {  
  4.                 reorderedSources.add(item);  
  5.             }  
  6.             // Maybe we should add before the DEFAULT_PROPERTIES if it exists?  
  7.             this.environment.getPropertySources().addLast(  
  8.                     new ConfigurationPropertySources(reorderedSources));  
  9.         }  

至此 application.xml等文件的加载分析结束。

 

时序图

简单的画了一下时序图,可能和实际调用存在出入,仅作参考使用 

《spring boot实战(第六篇)加载application资源文件源码分析》
 

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/zl1zl2zl3/article/details/79180108
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞