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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介:

在上一篇文章的结尾处我们简单的说了一下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);
相关文章
|
2月前
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
203 0
|
2月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
197 2
|
14天前
|
前端开发 Java Maven
深入解析:如何用 Spring Boot 实现分页和排序
深入解析:如何用 Spring Boot 实现分页和排序
32 2
|
3月前
|
存储 缓存 Java
在Spring Boot中使用缓存的技术解析
通过利用Spring Boot中的缓存支持,开发者可以轻松地实现高效和可扩展的缓存策略,进而提升应用的性能和用户体验。Spring Boot的声明式缓存抽象和对多种缓存技术的支持,使得集成和使用缓存变得前所未有的简单。无论是在开发新应用还是优化现有应用,合理地使用缓存都是提高性能的有效手段。
44 1
|
2月前
|
前端开发 JavaScript Java
【SpringBoot系列】视图解析器的搭建与开发
【SpringBoot系列】视图解析器的搭建与开发
36 0
|
24天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
64 2
|
2月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
72 0
|
2月前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
57 0
|
2月前
|
存储 Java C++
Collection-PriorityQueue源码解析
Collection-PriorityQueue源码解析
64 0
|
2月前
|
安全 Java 程序员
Collection-Stack&Queue源码解析
Collection-Stack&Queue源码解析
85 0

推荐镜像

更多