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

  1. 云栖社区>
  2. 博客>
  3. 正文

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

木叶之荣 2018-02-08 22:40:15 浏览1114
展开阅读全文

在上一篇文章的结尾处我们简单的说了一下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);

网友评论

登录后评论
0/500
评论
木叶之荣
+ 关注