zoukankan      html  css  js  c++  java
  • SpringBoot是如何加载配置文件的?

    前言

    本文针对版本2.2.0.RELEASE来分析SpringBoot的配置处理源码,通过查看SpringBoot的源码来弄清楚一些常见的问题比如:

    1. SpringBoot从哪里开始加载配置文件?
    2. SpringBoot从哪些地方加载配置文件?
    3. SpringBoot是如何支持yamlproperties类型的配置文件?
    4. 如果要支持json配置应该如何做?
    5. SpringBoot的配置优先级是怎么样的?
    6. placeholder是如何被解析的?

    带着我们的问题一起去看一下SpringBoot配置相关的源代码,找出问题的答案。

    SpringBoot从哪里开始加载配置文件?

    SpringBoot加载配置文件的入口是由ApplicationEnvironmentPreparedEvent事件进入的,SpringBoot会在SpringApplication的构造函数中通过spring.factories文件获取ApplicationListener的实例类:

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    	...
    	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        ...
    }
    

    spring.factories中有一个ConfigFileApplicationListener类,它会监听ApplicationEnvironmentPreparedEvent然后再加载配置文件 :

    # Application Listeners
    org.springframework.context.ApplicationListener= org.springframework.boot.context.config.ConfigFileApplicationListener
    ...
    

    有了事件和事件处理的类后,再找出发送事件的地方,就可以搞清楚SpringBoot是怎么加载配置文件的了,SpringBoot在启动之前先初始化好SpringApplicationRunListeners这个类,它会实现SpringApplicationRunListener接口然后对事件进行转发:

    class SpringApplicationRunListeners {
    
    	private final Log log;
    
    	private final List<SpringApplicationRunListener> listeners;
    
    	SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {
    		this.log = log;
    		this.listeners = new ArrayList<>(listeners);
    	}
     
    	void environmentPrepared(ConfigurableEnvironment environment) {
    		for (SpringApplicationRunListener listener : this.listeners) {
    			listener.environmentPrepared(environment);
    		}
    	}
    	...
    }
    

    获取SpringApplicationRunListeners的代码如下:

    
    private SpringApplicationRunListeners getRunListeners(String[] args) {
    	Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    	return new SpringApplicationRunListeners(logger,
    			getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
    }
    
    

    同样也会去加载spring.factories文件,该文件有一个EventPublishingRunListener类,该类的作用就是SpringBoot的事件转换成ApplicationEvent发送出去。

    # Run Listeners
    org.springframework.boot.SpringApplicationRunListener=
    org.springframework.boot.context.event.EventPublishingRunListener
    

    小结

    • SpringBoot会将事件转换成ApplicationEvent再分发
    • SpringBoot是通过监听ApplicationEnvironmentPreparedEvent事件来加载配置文件的
    • ConfigFileApplicationListener是处理配置文件的主要类

    SpringBoot从哪些地方加载配置文件?

    上面已经分析到ConfigFileApplicationListener是处理配置文件的主要类,然后进一步的查看SpringBoot是从哪些地址加载配置文件,进入ConfigFileApplicationListener类后会有两个默认的常量:

    private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
    private static final String DEFAULT_NAMES = "application";
    

    首先在没有任何配置的情况下,会从DEFAULT_SEARCH_LOCATIONS常量列出来的位置中加载文件名为DEFAULT_NAMES(.properties或yml)的文件,默认位置包括:

    • classpath根目录(classpath:/)
    • classpath里面的config文件目录(classpath:/config/)
    • 程序运行目录(file:./)
    • 程序运行目录下的config目录(file:./config/)

    上面说的是没有额外配置的情况,SpringBoot足够灵活可以指定配置文件搜索路径、配置文件名,在ConfigFileApplicationListener类中有个getSearchLocations方法,它主要负责获取配置搜索目录:

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

    它的操作步骤大致如下:

    1. 检查是否有spring.config.location属性,如果存在则直接使用它的值
    2. spring.config.additional-location属性中获取搜索路径
    3. 将默认搜索路径添加到搜索集合

    这里就可以确定SpringBoot配置的搜索路径有两种情况:如果配置了spring.config.location则直接使用,否则使用spring.config.additional-location的属性值 + 默认搜索路径。

    SpringBoot是如何支持yamlproperties类型的配置文件?

    SpringBoot的配置支持propertiesyaml文件,SpringBoot是如何解析这两种文件的呢,继续分析ConfigFileApplicationListener这个类,里面有个子类叫Loader加载配置文件主要的工作就是由这货负责,但是直接读取propertiesyaml并转换成PropertySource还是由里面的PropertySourceLoader负责:

    Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
    	...
    	this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
    			getClass().getClassLoader());
    }
    

    构造Loader对象的时候就会先加载PropertySourceLoader,加载方式还是从spring.factories中读取:

    # PropertySource Loaders
    org.springframework.boot.env.PropertySourceLoader=
    org.springframework.boot.env.PropertiesPropertySourceLoader,
    org.springframework.boot.env.YamlPropertySourceLoader
    

    其中配置了两个PropertySourceLoader的实现类:

    • PropertiesPropertySourceLoader
    • YamlPropertySourceLoader

    看名字就知道是分别负责propertiesyaml的啦。

    如果要支持json配置应该如何做?

    如果不喜欢propertiesyaml这两种格式,想要定义json做为配置文字格式可以直接定义json类型的PropertySourceLoader:

    public class JSONPropertySourceLoader implements PropertySourceLoader {
    
        @Override
        public String[] getFileExtensions() {
            return new String[] {"json"};
        }
        
        @Override
        public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
    
            if(resource == null || !resource.exists()){
                return Collections.emptyList();
            }
    
            Map<String, Object> configs = JSON.parseObject(resource.getInputStream(), Map.class);
    
            return Collections.singletonList(
                    new MapPropertySource(name, configs)
            );
        }
    }
    

    然后在resources目录里面建立个META-INF,再添加个spring.factories里面的内容如下:

    org.springframework.boot.env.PropertySourceLoader=
    com.csbaic.arch.spring.env.loader.JSONPropertySourceLoader
    

    最后在resources目录里面建个application.json的配置文件 :

    {
      "spring.application.name": "JSONConfig"
    }
    

    正常启动SpringBoot获取spring.applicaiton.name的配置的值就是JSONConfig

    2019-11-02 14:50:17.730  INFO 55275 --- [           main] c.c.a.spring.env.SpringEnvApplication    : JSONConfig
    

    SpringBoot的配置优先级是怎么样的?

    SpringBoot中有个PropertySource接口,专门用来保存属性常见的实现类有:

    • CommandLinePropertySource
    • MapPropertySource
    • SystemEnvironmentPropertySource
    • ....

    另外为了集中管理PropertySource还抽象出一个PropertySources接口,PropertySources就一个实现类叫:MutablePropertySources,它将所有的PropertySource都放置在一个名叫propertySourceList集合中,同时提供一些修改操作方法:

    public void addFirst(PropertySource<?> propertySource) {}
    public void addLast(PropertySource<?> propertySource) {}
    public void addBefore(String relativePropertySourceName, PropertySource<?> propertySource) {}
    public void addAfter(String relativePropertySourceName, PropertySource<?> propertySource) {}
    public int precedenceOf(PropertySource<?> propertySource) {	}
    public PropertySource<?> remove(String name) {}
    public void replace(String name, PropertySource<?> propertySource) {}
    

    所有的PropertySource都保存在propertySourceList中,越小的索引优先级越高,所以如果想要覆盖属性只要保证优化级够高就行。

    placeholder是如何被解析的?

    继续分析ConfigFileApplicationListenerLoader子类,在构造时还会创建一个PropertySourcesPlaceholdersResolver,placeholder的解析都由它来完成:

    Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
    
    	this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
    }
    

    分析PropertySourcesPlaceholdersResolver发现,真正完成解析是由PropertyPlaceholderHelper完成,PropertySourcesPlaceholdersResolver 在构造的时候就会创建一个PropertyPlaceholderHelper

    public PropertySourcesPlaceholdersResolver(Iterable<PropertySource<?>> sources, PropertyPlaceholderHelper helper) {
    	this.sources = sources;
    	this.helper = (helper != null) ? helper : new PropertyPlaceholderHelper(SystemPropertyUtils.PLACEHOLDER_PREFIX,
    			SystemPropertyUtils.PLACEHOLDER_SUFFIX, SystemPropertyUtils.VALUE_SEPARATOR, true);
    }
    

    PropertySourcesPlaceholdersResolver 在创建 PropertyPlaceholderHelper 的时候会传递三个参数:前缀、后缀、默认值分割符,分别由以下三个常量表示:

    public static final String PLACEHOLDER_PREFIX = "${";
    public static final String PLACEHOLDER_SUFFIX = "}";
    public static final String VALUE_SEPARATOR = ":";
    

    这样 PropertyPlaceholderHelper 在解析placeholder时就能知道以什么格式来解析比如:${spring.application.name}这个placeholder就会被解析成属性值。

    总结

    SpringBoot的配置非常灵活配置可以来自文件、环境变量、JVM系统属性、配置中心等等,SpringBoot通过
    PropertySourcePropertySources实现属性优先级、CRUD的统一管理,为开发者提供统一的配置抽象。




    《架构文摘》每天一篇架构领域重磅好文,涉及一线互联网公司应用架构(高可用、高性 能、高稳定)、大数据、机器学习等各个热门领域。

  • 相关阅读:
    WinInet中的FTP操作
    CodeIgniter 用户指南 版本 1.7.2
    《Windows Mobile实例开发》电子书提供下载
    程序静默安装的参数总结
    Select a table of certain webpage
    568A
    在IIS 5.1 或IIS6 中配置PHP 的FastCGI模式
    镁天三国育将篇
    镁天三国军事篇
    windows 环境下的 protoc 安装
  • 原文地址:https://www.cnblogs.com/xwgblog/p/11794207.html
Copyright © 2011-2022 走看看