SpringBoot源码解析(五)Environment配置优先级

上篇文章分析了PropertySource,描述它是一组属性的集合,算是对接下来Environment环节的铺垫,它可以看做项目的运行时环境,是系统运行的基础,最此之前,我们先来聊一下系统配置的优先级

回想一下之前的面试,有没有遇到过这样的问题:在一个springboot项目中,命令行参数、系统环境变量、配置文件,它们的优先级是怎么样的?这个问题也将在本文中,从源码的角度给出答案

本文的主角,即SpringApplication的run方法中,这行prepareEnvironment
在这里插入图片描述

这个方法的入参是SpringApplicationRunListeners和ApplicationArguments,ApplicationArguments我们上一篇文章介绍过,里面封装了应用的启动参数,而SpringApplicationRunListeners之前也分析过,里面存储了一个listeners列表,只包含一个元素EventPublishingRunListener,在前面的流程中,通过它内部的事件多播器,发布了应用启动事件ApplicationStartingEvent

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
        ConfigurableEnvironment environment = this.getOrCreateEnvironment();
        this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
        listeners.environmentPrepared((ConfigurableEnvironment)environment);
        this.bindToSpringApplication((ConfigurableEnvironment)environment);
        if (!this.isCustomEnvironment) {
            environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
        }

        ConfigurationPropertySources.attach((Environment)environment);
        return (ConfigurableEnvironment)environment;
    }

我们逐行分析,首先是getOrCreateEnvironment

    private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            return this.environment;
        } else {
            switch(this.webApplicationType) {
            case SERVLET:
                return new StandardServletEnvironment();
            case REACTIVE:
                return new StandardReactiveWebEnvironment();
            default:
                return new StandardEnvironment();
            }
        }
    }

通过应用类型来决定实例化哪个Environment的实现类,第一篇文章提到过推断应用类型的方式,普通springboot应用属于SERVLET类型,所以这里创建的是StandardServletEnvironment

它派生自StandardEnvironment,默认构造函数是空的,看到这样的代码,我们一般要跟进一下父类,有可能子类构造函数为空,但是在父类的构造函数中做了一些初始化,并调用了子类的某些方法,本类就属于这种类型

public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {
    ......
    ......
    public StandardServletEnvironment() {
    }

    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new StubPropertySource("servletConfigInitParams"));
        propertySources.addLast(new StubPropertySource("servletContextInitParams"));
        if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
            propertySources.addLast(new JndiPropertySource("jndiProperties"));
        }

        super.customizePropertySources(propertySources);
    }
	......
	......
}

父类的构造函数依然是空,它又派生自AbstractEnvironment

public class StandardEnvironment extends AbstractEnvironment {
	......
	......
    public StandardEnvironment() {
    }

    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new MapPropertySource("systemProperties", this.getSystemProperties()));
        propertySources.addLast(new SystemEnvironmentPropertySource("systemEnvironment", this.getSystemEnvironment()));
    }
}

继续跟进AbstractEnvironment的默认构造方法

public abstract class AbstractEnvironment implements ConfigurableEnvironment {
    ......
    ......
    private final MutablePropertySources propertySources = new MutablePropertySources();
    private final ConfigurablePropertyResolver propertyResolver;

    public AbstractEnvironment() {
        this.propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);
        this.customizePropertySources(this.propertySources);
    }

    protected void customizePropertySources(MutablePropertySources propertySources) {
    }
    ......
    ......

这个构造方法中,先初始化了一个PropertySourcesPropertyResolver,传入了propertySources,这个propertySources是一个final类型,在声明的时候初始化为MutablePropertySources

我们上篇文章单独介绍的PropertySource,是一组属性的集合,而这里的MutablePropertySources,就是PropertySource的容器,它会将系统运行的各个阶段、从不同途径产生的PropertySource,存储在内部的一个List中

public class MutablePropertySources implements PropertySources {

    private final List<PropertySource<?>> propertySourceList;

    public MutablePropertySources() {
        this.propertySourceList = new CopyOnWriteArrayList();
    }
    ......
    ......
}

而PropertySourcesPropertyResolver,顾名思义,就是一个解析器,它通过持有MutablePropertySources,间接地持有了系统所有的PropertySource,也就可以对其做遍历,进行各种处理、查询等

初始化PropertySourcesPropertyResolver后,紧接着调用了customizePropertySources方法,把MutablePropertySources传了进去,此时MutablePropertySources里面的PropertySource列表还是空的

由于这个方法在子类中也有实现,所以真正调用的是具体实现类型StandardServletEnvironment中的方法

    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new StubPropertySource("servletConfigInitParams"));
        propertySources.addLast(new StubPropertySource("servletContextInitParams"));
        if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
            propertySources.addLast(new JndiPropertySource("jndiProperties"));
        }

        super.customizePropertySources(propertySources);
    }

通过调用MutablePropertySources 的addLast方法,往内部PropertySource的列表末尾添加了两组空配置,名称分别为servletConfigInitParams和servletContextInitParams,JNDI现在用的很少了,所以后面这行暂且跳过
然后调用了父类的customizePropertySources方法

    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new MapPropertySource("systemProperties", this.getSystemProperties()));
        propertySources.addLast(new SystemEnvironmentPropertySource("systemEnvironment", this.getSystemEnvironment()));
    }

也添加了两组配置,其中systemProperties是系统属性,包括诸如JVM版本、JDK版本、启动的时候通过-D指定的参数等都在这里,取的是System.getProperties()

    public Map<String, Object> getSystemProperties() {
        try {
            return System.getProperties();
        }
        ......
    }

比如在启动类通过-D添加系统属性
在这里插入图片描述
解析后加载到systemProperties中
在这里插入图片描述

第二行systemEnvironment,添加的是系统的环境变量,包含我们配置的JAVA_HOME等
比如添加环境变量
在这里插入图片描述
然后解析到了systemEnvironment中
在这里插入图片描述
systemProperties和systemEnvironment解析完成后,getOrCreateEnvironment方法就结束了,最终返回了一个Environment,内部有四个PropertySource,前两个是空的,后两个分别存储了系统属性和环境变量
在这里插入图片描述
回到prepareEnvironment方法,再看下一行configureEnvironment
在这里插入图片描述
传入的第一个参数为刚创建好的Environment,第二个参数就是启动参数String args[]

    protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
        if (this.addConversionService) {
            ConversionService conversionService = ApplicationConversionService.getSharedInstance();
            environment.setConversionService((ConfigurableConversionService)conversionService);
        }

        this.configurePropertySources(environment, args);
        this.configureProfiles(environment, args);
    }

addConversionService属性在初始化SpringApplication的时候默认设置为true,所以会进if分支,创建了一个ApplicationConversionService,并赋给environment

这个ApplicationConversionService是一个转换器,负责类型转换等工作,我们之前在分析ApplicationStartingEvent的时候,有提到过一个监听器BackgroundPreinitializer,在该事件中会启动一些线程做一些初始化工作,其中有个线程,new了一个DefaultFormattingConversionService,它和当前的ApplicationConversionService有共同的继承结构,只是之前仅仅new了一个实例,并没有存储,目的是让JVM提前把相关的Class信息装载起来,以及提前完成一些静态变量、静态代码块的初始化工作,而这里的实例赋给了environment后续使用

然后看下一行代码configurePropertySources
在这里插入图片描述
目的就是把命令行参数也作为一个PropertySource存进environment中

这里最先初始化完成的environment是不包含名为commandLineArgs的配置的,所以会走else分支,调用addFirst方法,SimpleCommandLinePropertySource的构造流程我们上篇文章看过,最终会生成一个名为commandLineArgs的PropertySource

    protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
        MutablePropertySources sources = environment.getPropertySources();
        if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
            sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
        }

        if (this.addCommandLineProperties && args.length > 0) {
            String name = "commandLineArgs";
            if (sources.contains(name)) {
                PropertySource<?> source = sources.get(name);
                CompositePropertySource composite = new CompositePropertySource(name);
                composite.addPropertySource(new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
                composite.addPropertySource(source);
                sources.replace(name, composite);
            } else {
                sources.addFirst(new SimpleCommandLinePropertySource(args));
            }
        }

    }

这里有意思的就来了,我们之前往PropertySource列表添加配置,都是调用的addLast方法,而这里调用了addFirst
我们看下MutablePropertySources的方法列表,发现除了addFirst、addLast,还有addBefore、addAfter方法,可以在指定位置插入一个PropertySource

    public void addBefore(String relativePropertySourceName, PropertySource<?> propertySource) {
        this.assertLegalRelativeAddition(relativePropertySourceName, propertySource);
        this.removeIfPresent(propertySource);
        int index = this.assertPresentAndGetIndex(relativePropertySourceName);
        this.addAtIndex(index, propertySource);
    }

    public void addAfter(String relativePropertySourceName, PropertySource<?> propertySource) {
        this.assertLegalRelativeAddition(relativePropertySourceName, propertySource);
        this.removeIfPresent(propertySource);
        int index = this.assertPresentAndGetIndex(relativePropertySourceName);
        this.addAtIndex(index + 1, propertySource);
    }

本文开头分析过,MutablePropertySources持有所有的PropertySource,而初始化Environment的时候新建了一个解析器PropertySourcesPropertyResolver,把MutablePropertySources传给了它,那么这个Resolver是怎么获取一个属性呢,看下它的getProperty方法

    @Nullable
    public String getProperty(String key) {
        return (String)this.getProperty(key, String.class, true);
    }

    @Nullable
    protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
        if (this.propertySources != null) {
            Iterator var4 = this.propertySources.iterator();

            while(var4.hasNext()) {
                PropertySource<?> propertySource = (PropertySource)var4.next();
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Searching for key '" + key + "' in PropertySource '" + propertySource.getName() + "'");
                }

                Object value = propertySource.getProperty(key);
                if (value != null) {
                    if (resolveNestedPlaceholders && value instanceof String) {
                        value = this.resolveNestedPlaceholders((String)value);
                    }

                    this.logKeyFound(key, propertySource, value);
                    return this.convertValueIfNecessary(value, targetValueType);
                }
            }
        }

        if (this.logger.isTraceEnabled()) {
            this.logger.trace("Could not find key '" + key + "' in any property source");
        }

        return null;
    }

遍历内部的PropertySource列表,依次调用其getProperty方法,获取到属性后就返回,后面的PropertySource中即使有同名属性也不再处理了,看上去就像是前面的属性“覆盖”了后面的同名属性

List是个有序集合,所以PropertySource添加到List中的顺序,就决定了它的优先级,上面的命令行参数添加到List中使用的方法是addFirst,插入到了集合的最前面,所以命令行参数的优先级是最高的

既然了解了这个机制,想要自定义一批属性,让它拥有最高的优先级,只需要将其添加到PropertySource列表的最前面,而SpringBoot也提供了这样的扩展点,我们下篇文章再做介绍

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值