SpringBoot(十二)源码角度解析配置文件加载优先级

SpringBoot(十二)源码角度解析配置文件加载优先级,第1张

SpringBoot(十二)源码角度解析配置文件加载优先级

Spring Boot启动会扫描application.properties或者application.yml文件作为Spring Boot的默认配置文件。在使用过程中会涉及到各种各样的配置,本篇则主要针对配置路径,看一下文件加载的前后顺序。多个配置文件配置了同样得值,会加载哪一个呢?

一、内部配置的加载顺序

1)配置文件前后顺序执行测试
  • file:/config/

  • file:/

  • classpath:/config/

  • classpath:/
    以上顺序按照优先级从高到低的顺序,所有位置的文件都会被加载,高优先级的配置内容会覆盖低优先级配置的内容,其中配置文件中的内容是互补配置,即

  • 存在相同的配置内容,高优先级的内容会覆盖低优先级的内容

  • 存在不同的内容的时候,高优先级和低优先级的配置内容取并集

    我配置了如上几个配置文件,看项目启动结果,则可看到最终生效的配置文件。
    第一种情况:五个路径配置文件都存在

  • file:/config/
  • file:/
  • classpath:/config/
  • classpath:/
    结果为:

第二种:去除8025所在的file:/config/

  • file:/
  • classpath:/config/
  • classpath:/
    结果为:

第三种:再去除8026所在的file:/

  • classpath:/config/
  • classpath:/
    结果为:

第四种:再去除8024所在的配置文件,则只剩 classpath:/下的.properties和.yml两个配置文件了,看执行结果会如何呢?

  • classpath:/
    结果为:
二、外部配置的加载顺序

SpringBoot 既可以加载指定目录下的配置文件获取配置项,也可以通过启动参数(VM Options)传入配置项,为什么通过启动参数传入的配置项会“顶掉”配置文件中的配置?

三、prepareEnvironment原理解析

run方法下有如下方法:字面理解就是准备环境

ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
        //获取或创建环境(存在直接返回,不存在创建)
        ConfigurableEnvironment environment = this.getOrCreateEnvironment();
        //配置环境:配置PropertySources和activeProfiles
        this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
        //通知所有的监听者,环境已经准备好了
        listeners.environmentPrepared((ConfigurableEnvironment)environment);
        // bindToSpringApplication绑定环境 
        this.bindToSpringApplication((ConfigurableEnvironment)environment);
        // 如果是非web环境,将环境转换成StandardEnvironment
        if (!this.isCustomEnvironment) {
            environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
        }

        // 如果配置了configurationProperties属性, 那么将其放在environment的propertySources的首部
        ConfigurationPropertySources.attach((Environment)environment);
        return (ConfigurableEnvironment)environment;
    }
1)getOrCreateEnvironment

getOrCreateEnvironment方法创建并返回了一个环境:

private ConfigurableEnvironment getOrCreateEnvironment() {
        // 存在则直接返回
        if (this.environment != null) {
            return this.environment;
        } else {
            // 根据webApplicationType创建对应的Environment
            switch(this.webApplicationType) {
            // 标准的Servlet环境,也就是我们说的web环境
            case SERVLET:
                return new StandardServletEnvironment();
            case REACTIVE:
            //响应式web环境,2.0新引入
                return new StandardReactiveWebEnvironment();
            default:
            //普通程序
                return new StandardEnvironment();
            }
        }
    }

我们断点调试,则是返回StandardServletEnvironment

该环境目前包含的内容如下

2)configureEnvironment
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
        //默认为TRUE
        if (this.addConversionService) {
            //environment中配置各个转换类
            ConversionService conversionService = ApplicationConversionService.getSharedInstance();
            environment.setConversionService((ConfigurableConversionService)conversionService);
        }
        //配置属性
        this.configurePropertySources(environment, args);
        //配置profile
        this.configureProfiles(environment, args);
    }

从源码看,将配置任务按顺序委托给configurePropertySources和configureProfiles,分别配置属性和profile。
我们来看看configureProfiles,正常项目中,我们是分各种环境的,比如开发环境,测试环境,线上环境,有可能还有什么压测环境等等。就是通过此方法来查找对应的环境配置。

protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
        // 保证environment的activeProfiles属性被初始化了。从PropertySources中查找spring.profiles.active属性
        environment.getActiveProfiles();
        Set profiles = new linkedHashSet(this.additionalProfiles);
        //再次获取和配置
        profileprofiles.addAll(Arrays.asList(environment.getActiveProfiles()));
        //设置environment的profile
        environment.setActiveProfiles(StringUtils.toStringArray(profiles));
    }

configureEnvironment已经配置好了环境,下一步则是发送出去

3)listeners.environmentPrepared

调用SpringApplicationRunListeners的environmentPrepared方法发布事件。该方法会遍历注册在spring.factories中SpringApplicationRunListener实现类,然后调用其environmentPrepared方法。

每一个自动注入类中都有spring.factories,其中SpringApplicationRunListener实现类注册为:

 public void environmentPrepared(ConfigurableEnvironment environment) {
        Iterator var2 = this.listeners.iterator();

        while(var2.hasNext()) {
            SpringApplicationRunListener listener = (SpringApplicationRunListener)var2.next();
            listener.environmentPrepared(environment);
        }

    }

其中listeners便是注册的类的集合,这里默认只有EventPublishingRunListener。它的environmentPrepared方法实现为:

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

其实就是发布了一个监听事件。该事件会被同样注册在spring.factories中的ConfigFileApplicationListener监听到:

关于配置文件的核心处理便在ConfigFileApplicationListener中完成。在该类中我们可以看到很多熟悉的常量:

public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
    private static final String DEFAULT_PROPERTIES = "defaultProperties";
    private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
    private static final String DEFAULT_NAMES = "application";
    private static final Set NO_SEARCH_NAMES = Collections.singleton((Object)null);
    private static final Bindable STRING_ARRAY = Bindable.of(String[].class);
    public static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active";
    public static final String INCLUDE_PROFILES_PROPERTY = "spring.profiles.include";
    public static final String CONFIG_NAME_PROPERTY = "spring.config.name";
    public static final String CONFIG_LOCATION_PROPERTY = "spring.config.location";
    public static final String CONFIG_ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location";
    public static final int DEFAULT_ORDER = -2147483638;
    private final DeferredLog logger = new DeferredLog();
    private String searchLocations;
    private String names;
    private int order = -2147483638;

    public ConfigFileApplicationListener() {
    }

比如Spring Boot默认寻找的配置文件的名称、默认扫描的类路径等。

不仅如此,该类还实现了监听器SmartApplicationListener接口和EnvironmentPostProcessor接口也就是拥有了监听器和环境处理的功能。
设置断点调试则会发现,调试到environmentPrepared方法,直接跳转到ConfigFileApplicationListener中的onApplicationEvent上。

onApplicationEvent方法对接收到事件进行判断,如果是ApplicationEnvironmentPreparedEvent事件则调用onApplicationEnvironmentPreparedEvent方法进行处理
如下图:

onApplicationEnvironmentPreparedEvent会获取到对应的EnvironmentPostProcessor并调用其postProcessEnvironment方法进行处理。而loadPostProcessors方法获取的EnvironmentPostProcessor正是在spring.factories中配置的当前类。如下图:

经过一系列的调用,最终调用到该类的如下方法:

protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
        RandomValuePropertySource.addToEnvironment(environment);
        (new ConfigFileApplicationListener.Loader(environment, resourceLoader)).load();
    }

其中Loader类为ConfigFileApplicationListener内部类,提供了具体处理配置文件优先级、profile、加载解析等功能。
其中Loader中的Load方法,遍历PropertySourceLoader列表,并进行对应配置文件的解析,而这里的列表中的PropertySourceLoader同样配置在spring.factories中:

private void load(String location, String name, ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.documentFilterFactory filterFactory, ConfigFileApplicationListener.documentConsumer consumer) {
            if (!StringUtils.hasText(name)) {
                Iterator var6 = this.propertySourceLoaders.iterator();

                while(var6.hasNext()) {
                    PropertySourceLoader loader = (PropertySourceLoader)var6.next();
                    if (this.canLoadFileExtension(loader, location)) {
                        this.load(loader, location, profile, filterFactory.getdocumentFilter(profile), consumer);
                        return;
                    }
                }
            }

            Set processed = new HashSet();
            Iterator var14 = this.propertySourceLoaders.iterator();

            while(var14.hasNext()) {
                PropertySourceLoader loaderx = (PropertySourceLoader)var14.next();
                String[] var9 = loaderx.getFileExtensions();
                int var10 = var9.length;

                for(int var11 = 0; var11 < var10; ++var11) {
                    String fileExtension = var9[var11];
                    if (processed.add(fileExtension)) {
                        this.loadForFileExtension(loaderx, location + name, "." + fileExtension, profile, filterFactory, consumer);
                    }
                }
            }

        }

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

查看这两PropertySourceLoader的源代码,会发现SpringBoot默认支持的配置文件格式及解析方法。

这也就明白了最开始测试的时候,内部配置的先后顺序了。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5660003.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-16
下一篇 2022-12-16

发表评论

登录后才能评论

评论列表(0条)

保存