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);
相关文章
|
4月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
1735 0
|
3月前
|
前端开发 Java 微服务
《深入理解Spring》:Spring、Spring MVC与Spring Boot的深度解析
Spring Framework是Java生态的基石,提供IoC、AOP等核心功能;Spring MVC基于其构建,实现Web层MVC架构;Spring Boot则通过自动配置和内嵌服务器,极大简化了开发与部署。三者层层演进,Spring Boot并非替代,而是对前者的高效封装与增强,适用于微服务与快速开发,而深入理解Spring Framework有助于更好驾驭整体技术栈。
|
3月前
|
XML JSON Java
【SpringBoot(三)】从请求到响应再到视图解析与模板引擎,本文带你领悟SpringBoot请求接收全流程!
Springboot专栏第三章,从请求的接收到视图解析,再到thymeleaf模板引擎的使用! 本文带你领悟SpringBoot请求接收到渲染的使用全流程!
328 3
|
4月前
|
Java 数据库 数据安全/隐私保护
Spring Boot四层架构深度解析
本文详解Spring Boot四层架构(Controller-Service-DAO-Database)的核心思想与实战应用,涵盖职责划分、代码结构、依赖注入、事务管理及常见问题解决方案,助力构建高内聚、低耦合的企业级应用。
1080 1
|
10月前
|
存储 Java 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `&lt;appender&gt;` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `&lt;logger&gt;` 和 `&lt;root&gt;` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
2523 1
|
9月前
|
前端开发 安全 Java
Spring Boot 便利店销售系统项目分包设计解析
本文深入解析了基于Spring Boot的便利店销售系统分包设计,通过清晰的分层架构(表现层、业务逻辑层、数据访问层等)和模块化设计,提升了代码的可维护性、复用性和扩展性。具体分包结构包括`controller`、`service`、`repository`、`entity`、`dto`、`config`和`util`等模块,职责分明,便于团队协作与功能迭代。该设计为复杂企业级应用开发提供了实践参考。
372 0
|
6月前
|
前端开发 Java 数据库连接
SpringBoot参数校验底层原理和实操。深度历险、深度解析(图解+秒懂+史上最全)
SpringBoot参数校验底层原理和实操。深度历险、深度解析(图解+秒懂+史上最全)
SpringBoot参数校验底层原理和实操。深度历险、深度解析(图解+秒懂+史上最全)
|
6月前
|
机器学习/深度学习 XML Java
【spring boot logback】日志logback格式解析
在 Spring Boot 中,Logback 是默认的日志框架,它支持灵活的日志格式配置。通过配置 logback.xml 文件,可以定义日志的输出格式、日志级别、日志文件路径等。
1188 5
|
6月前
|
Java 关系型数据库 数据库连接
Spring Boot项目集成MyBatis Plus操作PostgreSQL全解析
集成 Spring Boot、PostgreSQL 和 MyBatis Plus 的步骤与 MyBatis 类似,只不过在 MyBatis Plus 中提供了更多的便利功能,如自动生成 SQL、分页查询、Wrapper 查询等。
671 3

热门文章

最新文章

推荐镜像

更多
  • DNS