SpringBoot之浅析配置项解析(五)

简介:

在上一篇文章的结尾处我们简单的说了一下PropertiesConfigurationFactory中的bindPropertiesToTarget这个方法的内容,在这个方法中有这样的一段代码:

 //获取PropertyValues 重点要分析的
PropertyValues propertyValues = getPropertySourcesPropertyValues(names,relaxedTargetNames);

首先先看一下我们在这个地方进行debug所看到的现象:
names
这里的names是SpringBoot根据我们在ConfigurationProperties中设置的prefix的值和属性的值所能兼容的配置项的key,这里一共有98种key存在。
relaxedTargetNames
上图中的是我们在ConfigurationProperties中设置的prefix所能匹配到的值的情况。我们进入到getPropertySourcesPropertyValues这个方法中看一下这个方法的内容:

    private PropertyValues getPropertySourcesPropertyValues(Set<String> names,
            Iterable<String> relaxedTargetNames) {
        //属性名字匹配器 1)
        PropertyNamePatternsMatcher includes = getPropertyNamePatternsMatcher(names,
                relaxedTargetNames);
        //重点要分析的内容 2)
        return new PropertySourcesPropertyValues(this.propertySources, names, includes,
                this.resolvePlaceholders);
    }

1)处的代码如下: 这里主要做的是获取配置项key的匹配器

    private PropertyNamePatternsMatcher getPropertyNamePatternsMatcher(Set<String> names,
            Iterable<String> relaxedTargetNames) {
        //ignoreUnknownFields忽略未知的属性  isMapTarget 目标属性是不是Map类型
        if (this.ignoreUnknownFields && !isMapTarget()) {
            // EXACT_DELIMITERS  { '_', '.', '[' }
            return new DefaultPropertyNamePatternsMatcher(EXACT_DELIMITERS, true, names);
        }
        //如果上面的条件都不满足的话 则走下面的逻辑
        //从上面的图中我们可以看到这里的relaxedTargetNames不为null 但是我们的属性不是为null
        if (relaxedTargetNames != null) {
            Set<String> relaxedNames = new HashSet<String>();
            for (String relaxedTargetName : relaxedTargetNames) {
                relaxedNames.add(relaxedTargetName);
            }
            //TARGET_NAME_DELIMITERS  { '_', '.' } 
            return new DefaultPropertyNamePatternsMatcher(TARGET_NAME_DELIMITERS, true,
                    relaxedNames);
        }
        //匹配所有的类型
        return PropertyNamePatternsMatcher.ALL;
    }

2)处的代码是我们要分析的一个重点,我们进入到PropertySourcesPropertyValues这个类的构造方法中看一下:

    PropertySourcesPropertyValues(PropertySources propertySources,
            Collection<String> nonEnumerableFallbackNames,
            PropertyNamePatternsMatcher includes, boolean resolvePlaceholders) {
        //这个否则函数中有四个参数propertySources是我们从环境变量中获取到的MutablePropertySources的实例 nonEnumerableFallbackNames是所有的配置项的key值 includes是我们上一步获取到的配置项的key的匹配器  resolvePlaceholders 是否解析占位符 如${} 这里的值是true 
        //再一次的判断 propertySources 不能为null
        Assert.notNull(propertySources, "PropertySources must not be null");
        Assert.notNull(includes, "Includes must not be null");
        this.propertySources = propertySources;
        this.nonEnumerableFallbackNames = nonEnumerableFallbackNames;
        this.includes = includes;
        this.resolvePlaceholders = resolvePlaceholders;
        //PropertySource属性解析器 这个类算是配置项解析这个大的体系中的一个类
        PropertySourcesPropertyResolver resolver = new PropertySourcesPropertyResolver(
                propertySources);
        //循环之前获取到的propertySources
        for (PropertySource<?> source : propertySources) {
            processPropertySource(source, resolver);
        }
    }

这里给出一个propertySources的debug截图信息:
propertySources
注意看黑框中的内容,看看黑框中的顺序你会有什么发现呢?在上面的代码中我们可以发现,这里会循环propertySources中的PropertySource来进行处理。
processPropertySource的内容如下:

    private void processPropertySource(PropertySource<?> source,
            PropertySourcesPropertyResolver resolver) {
        //如果PropertySource 为CompositePropertySource 
        if (source instanceof CompositePropertySource) {
            processCompositePropertySource((CompositePropertySource) source, resolver);
        }
        //如果为EnumerablePropertySource类型  上面的CompositePropertySource是EnumerablePropertySource的子类  我们这里所提到的PropertySource大多都是EnumerablePropertySource的子类 可被列举的PropertySource
        else if (source instanceof EnumerablePropertySource) {
            processEnumerablePropertySource((EnumerablePropertySource<?>) source,
                    resolver, this.includes);
        }
        else {
            processNonEnumerablePropertySource(source, resolver);
        }
    }

我们直接进入到processEnumerablePropertySource中看一下:

    private void processEnumerablePropertySource(EnumerablePropertySource<?> source,
            PropertySourcesPropertyResolver resolver,
            PropertyNamePatternsMatcher includes) {
        //如果有属性值 即有配置项的key存在    
        if (source.getPropertyNames().length > 0) {
            for (String propertyName : source.getPropertyNames()) {
                //配置项的key是否和之前预设的的key是否匹配
                if (includes.matches(propertyName)) {
                    //根据配置项的key获取 相应的value值
                    Object value = getEnumerableProperty(source, resolver, propertyName);
                    putIfAbsent(propertyName, value, source);
                }
            }
        }
    }

如果你从上面看到现在的话,那么在这里是不是会有一个疑问呢?因为这里是循环propertySources来获取配置项的值的,如果这样的话,那么后面的propertySources中的值岂不是会覆盖前面的propertySources中的值吗?但是我们看到的现象却又不是这样的,这又是怎么一回事呢?秘密就在getEnumerableProperty中。

Object value = getEnumerableProperty(source, resolver, propertyName);

    private Object getEnumerableProperty(EnumerablePropertySource<?> source,
            PropertySourcesPropertyResolver resolver, String propertyName) {
        try {
            if (this.resolvePlaceholders) {
                return resolver.getProperty(propertyName, Object.class);
            }
        }
        return source.getProperty(propertyName);
    }
    
    //org.springframework.core.env.PropertySourcesPropertyResolver#getProperty(java.lang.String, java.lang.Class<T>)
    @Override
    public <T> T getProperty(String key, Class<T> targetValueType) {
        return getProperty(key, targetValueType, true);
    }
    
    //org.springframework.core.env.PropertySourcesPropertyResolver#getProperty(java.lang.String, java.lang.Class<T>, boolean)
    protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
        if (this.propertySources != null) {
            //奥秘就在这里!!!这里有循环了一次propertySources 
            for (PropertySource<?> propertySource : this.propertySources) {
                //如果能取到值的话 就直接返回获取到的值
                //所以如果你在前面的PropertySource中获取到值的话,那么后面的PropertySource中的值就不会再进行获取了!!!!!!!
                Object value = propertySource.getProperty(key);
                if (value != null) {
                    //解析占位符,这里就不再多说了,
                    if (resolveNestedPlaceholders && value instanceof String) {
                        value = resolveNestedPlaceholders((String) value);
                    }
                    //是否需要转换服务,这里就不再多说了 因为这里就是另外一个话题了  是否需要进行转换 如日期类型转换
                    return convertValueIfNecessary(value, targetValueType);
                }
            }
        }
        return null;
    }

所以到这里我们可以看明白为什么优先级高的配置项会最终生效的原因。以上代码简化之后就是这样的:

for (PropertySource<?> propertySource : this.propertySources) {
    for (PropertySource<?> propertySource : this.propertySources) {
        //如果获取到值就直接return    
    }
}

在processEnumerablePropertySource中还有一个这样的方法:

putIfAbsent(propertyName, value, source);

    private PropertyValue putIfAbsent(String propertyName, Object value,
            PropertySource<?> source) {
        //如果获取到了value值 并且在propertyValues不存在的话
        if (value != null && !this.propertyValues.containsKey(propertyName)) {
            //大家可以看一下关于List这样的属性是怎么进行属性值的设置的
            PropertySource<?> collectionOwner = this.collectionOwners.putIfAbsent(
                    COLLECTION_PROPERTY.matcher(propertyName).replaceAll("[]"), source);
            //如果没有获取过这个属性值的话 则放入到propertyValues中
            if (collectionOwner == null || collectionOwner == source) {
                PropertyValue propertyValue = new OriginCapablePropertyValue(propertyName,
                        value, propertyName, source);
                this.propertyValues.put(propertyName, propertyValue);
                return propertyValue;
            }
        }
        return null;
    }

真正的属性值的设置的动作是在org.springframework.boot.bind.PropertiesConfigurationFactory#doBindPropertiesToTarget这个方法中的这句话中完成的:

dataBinder.bind(propertyValues);
相关文章
|
Android开发 开发工具
android 获取系统硬件信息
一,首先设置权限访问:    二,逻辑代码获取系统硬件信息: package com.wangfubin.getmyphoneinformation; import org.w3c.dom.
1642 0
|
5G UED
5G NR中的寻呼过程
【8月更文挑战第31天】
679 1
|
测试技术 传感器 uml
带你读《基于模型的测试:一个软件工艺师的方法》之一:基于模型测试概述
本书主要讨论基于模型的测试(MBT)技术。作为一门手艺而非艺术,其关键在于:对被测软件或系统的理解,选择合适工具的能力,以及使用这些工具的经验。围绕这三个方面,书中不仅综合阐述了MBT的理论知识及工具,而且分享了作者的实战经验。
|
存储 算法 C语言
【C 言专栏】用 C 语言开发游戏的实践
【5月更文挑战第5天】本文探讨了使用C语言开发游戏的实践,包括选择适合的游戏类型(如贪吃蛇、俄罗斯方块),设计游戏框架、图形界面和逻辑,以及音效添加。文章还强调了性能优化、测试调试、跨平台挑战及未来发展趋势。对于热衷于C语言的开发者,这是一次挑战与乐趣并存的探索之旅。
647 0
【C 言专栏】用 C 语言开发游戏的实践
|
弹性计算
阿里云服务器ECS公网带宽最高只能选100M吗?
阿里云服务器ECS公网带宽最高只能选100M吗?阿里云服务器公网带宽最高只有100Mbps,不够用怎么办?阿里云的带宽居然只有100M,对于需要大带宽云服务器的用户怎么办?难道只能租物理机吗?并不是,可以将云服务器固定公网IP转成弹性公网EIP,弹性公网IP带宽最高可选1000Mbps,完全够用。云服务器吧来详细说下阿里云服务器公网带宽上限值的问题:
阿里云服务器ECS公网带宽最高只能选100M吗?
|
程序员 调度 开发工具
DOS系统
【10月更文挑战第15天】DOS系统
1538 3
|
Oracle Java 关系型数据库
三分钟拿下dbeaver企业版
数据库管理工具Dbeaver,开源的企业版,功能丰富
4306 0
三分钟拿下dbeaver企业版
|
SQL 数据挖掘 数据库
数据分析三剑客【AIoT阶段一(下)】(十万字博文 保姆级讲解)—Pandas—pandas进阶(十九)
你好,感谢你能点进来本篇博客,请不要着急退出,相信我,如果你有一定的 Python 基础,想要学习 Python数据分析的三大库:numpy,pandas,matplotlib;这篇文章不会让你失望,本篇博客是 【AIoT阶段一(下)】 的内容:Python数据分析,
442 0
数据分析三剑客【AIoT阶段一(下)】(十万字博文 保姆级讲解)—Pandas—pandas进阶(十九)
|
存储 SQL 程序员
Python 字符串str详解(超详细)(一)
Python 字符串str详解(超详细)(一)
1377 0
|
安全
无需光缆,最远20公里,网速20Gbps!谷歌母公司Alphabet尝试用光束建立宽带
谷歌母公司 Alphabet 的 X 部门的「登月工厂」 (Moonshot Factory)在开发许多尖端的黑科技,
409 0
无需光缆,最远20公里,网速20Gbps!谷歌母公司Alphabet尝试用光束建立宽带

热门文章

最新文章