Spring源码之:占位符解析

595 阅读7分钟

1. 概述

我们知道,@Value注解的value值可以是个占位符表达式,如@Value("${name:zs}")
而占位符表达式是通过PropertyPlaceholderHelper组件来解析的
我们先通过一个例子来了解一下该组件的使用:

public class PropertyPlaceholderHelperDemo {
    public static void main(String[] args) {

        // 创建一个PropertyPlaceholderHelper组件,其中:
        // 1. "${"和"}"分别代表占位符的前缀和后缀;此时,表达式"${name}"会被解析为键"name"对应的值
        // 2. ":"代表占位符和默认值的分隔符;如果表达式是"${name:zs}",且找不到键"name"对应的值,则该表达式会被解析为"zs"
        // 3. true代表忽略无法解析的占位符;在无法解析占位符的情况下,该组件不会抛异常,而是继续往后解析
        PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper("${", "}", ":", true);

        // 先准备好各个属性对应的值
        Properties properties = new Properties();
        properties.setProperty("one", "1");
        properties.setProperty("name1", "zhangsan");
        properties.setProperty("name2", "lisi");

        // 读取键"one"对应的值;这里打印"1"
        System.out.println(helper.replacePlaceholders("${one}", properties));

        // 读取键"two"对应的值;由于找不到该键对应的值,且第四个参数为true,因此不会解析该表达式;这里打印"${two}"
        System.out.println(helper.replacePlaceholders("${two}", properties));

        // 占位符可以嵌套占位符;这里会先将"${one}"解析成"1",然后解析"${name1}";这里打印"zhangsan"
        System.out.println(helper.replacePlaceholders("${name${one}}", properties));

        // 占位符可以有默认值;在解析"${two:2}"时,由于找不到键"two"对应的值,因此会将该表达式解析为"2";这里打印"lisi"
        System.out.println(helper.replacePlaceholders("${name${two:2}}", properties));

        // 默认值还可以是另一个表达式;这里会将"${first:${one}}"解析为${first:1},因此打印"zhangsan"
        System.out.println(helper.replacePlaceholders("${name${first:${one}}}", properties));

        // 键对应的值也可以是另一个表达式;这里将"${expr}"解析成"${one}"后,还会对"${one}"进行解析;这里打印"1"
        properties.setProperty("expr", "${one}");
        System.out.println(helper.replacePlaceholders("${expr}", properties));

        // 再来个骚操作,通过多个表达式来拼接出另一个表达式,此时是无法解析最终的表达式的;这里打印"${one}"
        properties.setProperty("prefix", "${");
        properties.setProperty("suffix", "}");
        System.out.println(helper.replacePlaceholders("${prefix}one${suffix}", properties));

        // 既然上面的表达式被解析成了"${one}",那用"${...}"将上面的表达式包起来(如下),能不能最终解析到"1"呢?
        // 答案是不能,因为"${prefix}one${suffix}"被解析成"${one}"后,该组件会直接以"${one}"为键在properties中找对应的值
        // 由于无法找到键"${one}"对应的值,因此解析失败;由于第四个参数为true,因此这里打印"${${prefix}one${suffix}}"
        // 也就是说,通过字符串拼接而得到的占位符表达式是不会被PropertyPlaceholderHelper解析的
        System.out.println(helper.replacePlaceholders("${${prefix}one${suffix}}", properties));

        // 当第四个参数为true时,这里打印"wangwu";而当第四个参数为false时,这里会抛异常,因为找不到键"three"对应的值
        properties.setProperty("name${three}", "wangwu");
        System.out.println(helper.replacePlaceholders("${name${three}}", properties));
    }
}

2. PropertyPlaceholderHelper

2.1. PlaceholderResolver

public class PropertyPlaceholderHelper {
    
    /**
     * 本类的作用和上面例子中Properties组件的作用相同,都是用于将key转成对应的value
     */
    @FunctionalInterface
    public interface PlaceholderResolver {

        /**
         * 获取到占位符对应的真实值,如:"name" -> "zhangsan"
         */
        @Nullable
        String resolvePlaceholder(String placeholderName);
    }
}

2.2. 成员变量和构造方法

public class PropertyPlaceholderHelper {

    /**
     * 记录一些常用的后缀及其对应的前缀
     */
    private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<>(4);
    static {
        wellKnownSimplePrefixes.put("}", "{");
        wellKnownSimplePrefixes.put("]", "[");
        wellKnownSimplePrefixes.put(")", "(");
    }

    /**
     * 占位符的前缀
     */
    private final String placeholderPrefix;

    /**
     * 占位符的后缀
     */
    private final String placeholderSuffix;

    /**
     * 占位符的前缀的简写;假如前缀为"${",且后缀为"}",则前缀的简写为"{"
     */
    private final String simplePrefix;

    /**
     * 占位符名称与默认值的分隔符;如果为null,则不支持默认值功能
     */
    @Nullable
    private final String valueSeparator;

    /**
     * 是否忽略无法解析的占位符
     */
    private final boolean ignoreUnresolvablePlaceholders;

    /**
     * 构造方法;需指定前缀和后缀;默认不支持默认值功能,并且忽略无法解析的占位符
     */
    public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix) {
        this(placeholderPrefix, placeholderSuffix, null, true);
    }

    /**
     * 构造方法,由用户自己指定4个参数的值
     */
    public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix,
                                     @Nullable String valueSeparator, boolean ignoreUnresolvablePlaceholders) {

        Assert.notNull(placeholderPrefix, "'placeholderPrefix' must not be null");
        Assert.notNull(placeholderSuffix, "'placeholderSuffix' must not be null");

        this.placeholderPrefix = placeholderPrefix;
        this.placeholderSuffix = placeholderSuffix;
        this.valueSeparator = valueSeparator;
        this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;

        // 设置this.simplePrefix的值
        // 如果后缀是右括号,且前缀以对应的左括号结尾,则将this.simplePrefix指定为该左括号
        // 否则,将this.simplePrefix指定为用户自定义的前缀
        // 比如:后缀为")",前缀为"abc(",则this.simplePrefix为"("
        String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix);
        if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) {
            this.simplePrefix = simplePrefixForSuffix;
        } else {
            this.simplePrefix = this.placeholderPrefix;
        }
    }
}

2.3. findPlaceholderEndIndex()

public class PropertyPlaceholderHelper {

    /**
     * 找到能与占位符前缀匹配的占位符后缀的下标,需要考虑嵌套占位符的情况
     * 其中,buf代表待解析的文本(可能会包含嵌套占位符),startIndex代表占位符前缀的起始下标
     * 假设buf为"${name${one}}",那么:当startIndex为0时,返回12;当startIndex为6时,返回11
     */
    private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
        int index = startIndex + this.placeholderPrefix.length();

        // 嵌套占位符个数
        int withinNestedPlaceholder = 0;

        while (index < buf.length()) {

            // 如果找到一个后缀
            if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) {

                // 如果嵌套占位符个数大于0,则该后缀属于当前最内部的嵌套占位符,因此withinNestedPlaceholder减一,然后继续查找后缀
                if (withinNestedPlaceholder > 0) {
                    withinNestedPlaceholder--;
                    index = index + this.placeholderSuffix.length();

                // 否则,当前后缀的下标就是要找的下标
                } else {
                    return index;
                }

            // 如果找到一个前缀简称,此时认为找到了一个嵌套的占位符
            // 也就是说,"${a{b}}"中的"{b}"在这里会被视为一个嵌套占位符
            // 因此,如果buf为"${a{b}}",且startIndex为0,那么本方法将会返回6,而不是5
            // 需要注意的是,"{b}"并不是一个合法的占位符表达式;因此,在解析阶段,"{b}"将会被当作纯文本,而不是占位符
            } else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) {
                withinNestedPlaceholder++;
                index = index + this.simplePrefix.length();

            // 否则,光标指向下一个字符
            } else {
                index++;
            }
        }

        // 执行到这里,说明找不到对应的后缀
        return -1;
    }
}

2.4. replacePlaceholders()

public class PropertyPlaceholderHelper {

    /**
     * 解析占位符;value代表待解析的表达式,properties是数据源
     */
    public String replacePlaceholders(String value, final Properties properties) {
        Assert.notNull(properties, "'properties' must not be null");
        
        // 通过lambda表达式将properties转成PlaceholderResolver形式,然后调用另一个重载方法
        return replacePlaceholders(value, properties::getProperty);
    }

    /**
     * 解析占位符;value代表待解析的表达式,placeholderResolver是数据源
     */
    public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
        Assert.notNull(value, "'value' must not be null");
        
        // 调用parseStringValue()方法来解析占位符
        return parseStringValue(value, placeholderResolver, null);
    }

    /**
     * 解析占位符;value代表待解析的表达式,placeholderResolver是数据源
     * 这里的visitedPlaceholders集合用于保存正在解析的占位符,主要用于检测循环引用,避免无限递归
     * 一个简单的循环引用的例子:键"expr"对应的值为"${expr}",因此,"${expr}"会被解析为"${expr}",导致死循环
     */
    protected String parseStringValue(
            String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {

        // 获取到表达式中第一个占位符前缀的下标;如果没找到,说明没有占位符,因此返回原字符串
        int startIndex = value.indexOf(this.placeholderPrefix);
        if (startIndex == -1) {
            return value;
        }

        // 将表达式转成StringBuilder形式,方便进行替换等操作
        StringBuilder result = new StringBuilder(value);
        
        while (startIndex != -1) {
            
            // 找到当前的前缀对应的后缀的下标
            int endIndex = findPlaceholderEndIndex(result, startIndex);
            
            // 如果确实有对应的后缀
            if (endIndex != -1) {
                
                // 截取出前缀和后缀之间的字符串placeholder
                String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
                String originalPlaceholder = placeholder;
                
                // 将placeholder保存到visitedPlaceholders中
                // 如果placeholder已经在visitedPlaceholders中了,说明出现了循环引用,因此直接抛异常
                if (visitedPlaceholders == null) {
                    visitedPlaceholders = new HashSet<>(4);
                }
                if (!visitedPlaceholders.add(originalPlaceholder)) {
                    throw new IllegalArgumentException(
                            "Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
                }
                
                // 由于placeholder中可能包含嵌套占位符,因此递归调用parseStringValue()方法,对其中的占位符进行解析
                placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
                
                // 此时的placeholder中的嵌套表达式都已经解析完成了,因此在数据源中查找placeholder对应的值
                String propVal = placeholderResolver.resolvePlaceholder(placeholder);
                
                // 如果没有找到对应的值,且this.valueSeparator不为null
                // 说明此时placeholder有可能提供了默认值,不能直接将placeholder传给placeholderResolver来解析
                if (propVal == null && this.valueSeparator != null) {
                    
                    // 判断placeholder中是否真的有分隔符,如果有,则进一步解析
                    int separatorIndex = placeholder.indexOf(this.valueSeparator);
                    if (separatorIndex != -1) {
                        
                        // 分隔符前面的才是真正的占位符,后面的是默认值
                        String actualPlaceholder = placeholder.substring(0, separatorIndex);
                        String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
                        
                        // 解析占位符,如果解析不到,则取默认值
                        propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
                        if (propVal == null) {
                            propVal = defaultValue;
                        }
                    }
                }
                
                // 如果解析到了对应的值
                if (propVal != null) {
                    
                    // 此时的propVal依旧有可能包含占位符,因此还需要继续解析
                    propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
                    
                    // 这时候的propVal才是真正解析到的数据,因此用该值替换当前表达式,然后更新startIndex,准备解析下一个占位符
                    result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
                    startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
                
                // 否则,如果允许忽略无法解析的占位符,则更新startIndex,准备解析下一个占位符
                } else if (this.ignoreUnresolvablePlaceholders) {
                    startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
                
                // 否则直接抛异常
                } else {
                    throw new IllegalArgumentException("Could not resolve placeholder '" +
                            placeholder + "'" + " in value \"" + value + "\"");
                }
                
                // 当前占位符已经解析完成,因此将其从visitedPlaceholders中移除
                visitedPlaceholders.remove(originalPlaceholder);
            
            // 如果没有找到对应的后缀,则结束循环
            } else {
                startIndex = -1;
            }
        }
        
        // 返回解析结果
        return result.toString();
    }
}

3. PropertySourcesPlaceholderConfigurer

该组件负责解析BeanDefinition中的占位符,比如,把数据库连接池底层的${jdbc.url}解析成真正的数据库连接地址
而要了解该组件的源码,就要先看它的所有祖先类,如PropertiesLoaderSupportPropertyResourceConfigurer

3.1. PropertiesLoaderSupport

/**
 * PropertiesLoaderSupport可以从指定的文件中加载配置信息
 * 同时有自己的本地配置信息,并且可以设置本地配置信息的优先级
 */
public abstract class PropertiesLoaderSupport {

    /**
     * 本地的配置信息;比如,Spring的配置文件的props标签中的内容
     */
    @Nullable
    protected Properties[] localProperties;

    /**
     * 本地的配置信息是否要覆盖文件中的配置信息;默认为false,代表文件中的配置信息的优先级更高
     */
    protected boolean localOverride = false;

    /**
     * 需要加载的文件;支持properties文件和xml文件;后面文件的配置会覆盖前面文件的配置
     */
    @Nullable
    private Resource[] locations;

    /**
     * 如果找不到目标文件,是否忽略该文件
     */
    private boolean ignoreResourceNotFound = false;

    /**
     * 文件编码;只对properties文件生效
     */
    @Nullable
    private String fileEncoding;

    /**
     * 属性持久化器;该组件负责读取properties/xml文件,并将其中的配置信息读取到Properties对象中
     */
    private PropertiesPersister propertiesPersister = ResourcePropertiesPersister.INSTANCE;

    // 以上字段都有相应的set方法,这里省略了

    /**
     * 加载目标文件,并将读取结果与本地配置信息进行合并,并返回合并后的结果
     */
    protected Properties mergeProperties() throws IOException {
        
        // 最终要返回的结果
        Properties result = new Properties();

        // 如果本地配置覆盖资源中的配置,则先将资源中的配置加载到result中
        // loadProperties()方法很简单,这里就直接省略了;感兴趣的可以自行查看源码
        if (this.localOverride) {
            loadProperties(result);
        }

        // 将本地配置添加到result中
        if (this.localProperties != null) {
            for (Properties localProp : this.localProperties) {
                CollectionUtils.mergePropertiesIntoMap(localProp, result);
            }
        }

        // 如果不允许本地配置覆盖资源中的配置,则在这时才将资源中的配置加载到result中
        if (!this.localOverride) {
            loadProperties(result);
        }

        return result;
    }
}

3.2. PropertyResourceConfigurer

/**
 * 本类继承自PropertiesLoaderSupport,并实现了BeanFactoryPostProcessor接口
 * 
 * 本类可以对BeanFactory(主要是它里面的BeanDefinition)进行后置处理;主要有两个子类:
 * 1. PropertyOverrideConfigurer:用"beanName.property=value"风格的配置来给Bean的指定字段赋值
 * 2. PropertyPlaceholderConfigurer:将BeanDefinition中的占位符表达式"${...}"替换成实际值
 * 
 * 本类还支持对合并后的属性值进行转换,比如对加密的数据进行解密,等等
 */
public abstract class PropertyResourceConfigurer extends PropertiesLoaderSupport
        implements BeanFactoryPostProcessor, PriorityOrdered {

    /**
     * 排序;默认为PriorityOrdered中的最低优先级
     */
    private int order = Ordered.LOWEST_PRECEDENCE;
    
    // 省略setOrder()和getOrder()方法

    /**
     * 对BeanFactory进行后置处理
     */
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        try {
            // 获取合并后的配置信息
            Properties mergedProps = mergeProperties();

            // 必要的话,对属性值进行转换(加密解密之类的)
            convertProperties(mergedProps);

            // 真正处理BeanFactory;该方法为抽象方法,需要子类实现
            processProperties(beanFactory, mergedProps);
        } catch (IOException ex) {
            throw new BeanInitializationException("Could not load properties", ex);
        }
    }

    /**
     * 必要的话,对属性值进行转换
     */
    protected void convertProperties(Properties props) {
        Enumeration<?> propertyNames = props.propertyNames();
        while (propertyNames.hasMoreElements()) {
            String propertyName = (String) propertyNames.nextElement();
            String propertyValue = props.getProperty(propertyName);
            
            // 对当前属性值进行转换;如果确实转换成了另一个值,则用转换后的结果替换原来的结果
            String convertedValue = convertProperty(propertyName, propertyValue);
            if (!ObjectUtils.nullSafeEquals(propertyValue, convertedValue)) {
                props.setProperty(propertyName, convertedValue);
            }
        }
    }

    /**
     * 对单个属性值进行转换
     */
    protected String convertProperty(String propertyName, String propertyValue) {
        return convertPropertyValue(propertyValue);
    }

    /**
     * 对单个属性值进行转换;这里直接返回原来的属性值
     */
    protected String convertPropertyValue(String originalValue) {
        return originalValue;
    }

    /**
     * 根据配置信息来处理BeanFactory
     */
    protected abstract void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props)
            throws BeansException;
}

3.3. PlaceholderConfigurerSupport

/**
 * 本类继承自PropertyResourceConfigurer,但并没有实现父类的抽象方法
 * 本类为子类提供了与占位符相关的字段和方法,同时提供了一个现成的方法来替换BeanDefinition中的占位符
 */
public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfigurer
        implements BeanNameAware, BeanFactoryAware {

    // 占位符表达式默认的前缀、后缀、默认值分隔符
    public static final String DEFAULT_PLACEHOLDER_PREFIX = "${";
    public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}";
    public static final String DEFAULT_VALUE_SEPARATOR = ":";

    // 占位符的4项基本配置
    protected String placeholderPrefix = DEFAULT_PLACEHOLDER_PREFIX;
    protected String placeholderSuffix = DEFAULT_PLACEHOLDER_SUFFIX;
    protected String valueSeparator = DEFAULT_VALUE_SEPARATOR;
    protected boolean ignoreUnresolvablePlaceholders = false;

    /**
     * 是否对解析到的值进行trim操作
     */
    protected boolean trimValues = false;

    /**
     * 用于代表null的值
     */
    @Nullable
    protected String nullValue;

    /**
     * 与BeanNameAware接口相关
     */
    @Nullable
    private String beanName;

    /**
     * 与BeanFactoryAware接口相关
     */
    @Nullable
    private BeanFactory beanFactory;

    // 省略以上字段的set方法

    /**
     * 真正处理BeanFactory的方法;负责将BeanDefinition中的占位符替换成实际值
     */
    protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
            StringValueResolver valueResolver) {

        // 创建一个BeanDefinitionVisitor组件,负责解析和替换BeanDefinition中的占位符
        // BeanDefinitionVisitor组件底层持有一个StringValueResolver组件
        BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);

        // 获取所有的Bean名称,并逐个遍历
        String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
        for (String curName : beanNames) {
            
            // Check that we're not parsing our own bean definition,
            // to avoid failing on unresolvable placeholders in properties file locations.
            if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
                
                // 拿到对应的BeanDefinition,然后用BeanDefinitionVisitor组件处理该BeanDefinition
                // 这一步中,会对BeanDefinition中的parentName、beanClassName等属性的值进行占位符替换
                // 具体的处理过程比较简单,感兴趣的可以自行翻阅源码
                BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
                try {
                    visitor.visitBeanDefinition(bd);
                } catch (Exception ex) {
                    throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
                }
            }
        }

        // New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
        beanFactoryToProcess.resolveAliases(valueResolver);

        // New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
        beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
    }
}

3.4. PropertySourcesPlaceholderConfigurer

/**
 * 负责将BeanFactory中的BeanDefinition中的占位符替换成实际值;@Value注解中的占位符表达式也是由该组件来解析的
 * 占位符的数据来源有:Spring的环境(Environment)、通过mergeProperties()方法获取到的合并后的Properties对象
 */
public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerSupport implements EnvironmentAware {

    /**
     * 合并后的Properties对象(相当于本地配置信息)将会被封装成一个属性源;该值就代表这个属性源的名称
     */
    public static final String LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME = "localProperties";

    /**
     * Spring的Environment对象也会被封装成一个属性源;该值就代表这个属性源的名称
     */
    public static final String ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME = "environmentProperties";

    /**
     * 进行占位符替换时,使用的属性源集合
     */
    @Nullable
    private MutablePropertySources propertySources;

    /**
     * 被使用的属性源集合;该字段只有在占位符替换完成时才会被赋值
     */
    @Nullable
    private PropertySources appliedPropertySources;

    /**
     * 和EnvironmentAware接口相关
     */
    @Nullable
    private Environment environment;

    /**
     * 手动指定要使用的数据源集合
     * 如果不指定,则会从Environment和合并后的Properties对象中读取数据
     */
    public void setPropertySources(PropertySources propertySources) {
        this.propertySources = new MutablePropertySources(propertySources);
    }

    /**
     * 接收Spring的环境信息
     */
    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    /**
     * 重写PropertyResourceConfigurer类的postProcessBeanFactory()方法
     */
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        
        // 如果用户没有手动指定属性源集合
        if (this.propertySources == null) {
            
            // 创建一个属性源集合
            this.propertySources = new MutablePropertySources();
            
            // 将Environment封装成一个属性源,并添加到this.propertySources中
            if (this.environment != null) {
                this.propertySources.addLast(
                    new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
                        @Override
                        @Nullable
                        public String getProperty(String key) {
                            return this.source.getProperty(key);
                        }
                    }
                );
            }
            
            // 将合并后的Properties也封装成一个属性源,并根据this.localOverride的值将这个属性源添加到集合的开头或末尾
            try {
                PropertySource<?> localPropertySource =
                        new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
                if (this.localOverride) {
                    this.propertySources.addFirst(localPropertySource);
                } else {
                    this.propertySources.addLast(localPropertySource);
                }
            } catch (IOException ex) {
                throw new BeanInitializationException("Could not load properties", ex);
            }
        }

        // 创建PropertySourcesPropertyResolver组件;该组件相当于属性源和占位符解析器的组合
        // 然后调用自己的processProperties()方法来处理BeanFactory
        processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
        
        // 处理完成,将this.propertySources赋值给this.appliedPropertySources
        this.appliedPropertySources = this.propertySources;
    }

    /**
     * 由于PropertyResourceConfigurer类的postProcessBeanFactory()方法被重写了,因此该类的抽象方法也不需要有具体实现了
     */
    @Override
    @Deprecated
    protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props) {
        throw new UnsupportedOperationException(
                "Call processProperties(ConfigurableListableBeanFactory, ConfigurablePropertyResolver) instead");
    }

    /**
     * 本类自己定义的processProperties()方法
     */
    protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
            final ConfigurablePropertyResolver propertyResolver) throws BeansException {

        // 设置自定义的占位符前缀、后缀、默认值分隔符
        // propertyResolver底层的占位符解析器会在第一次解析占位符时才被初始化
        propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
        propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
        propertyResolver.setValueSeparator(this.valueSeparator);

        // 将ConfigurablePropertyResolver组件封装成StringValueResolver组件
        StringValueResolver valueResolver = strVal -> {
            String resolved = (this.ignoreUnresolvablePlaceholders ?
                    propertyResolver.resolvePlaceholders(strVal) :
                    propertyResolver.resolveRequiredPlaceholders(strVal));
            if (this.trimValues) {
                resolved = resolved.trim();
            }
            return (resolved.equals(this.nullValue) ? null : resolved);
        };

        // 调用父类的doProcessProperties()方法进行占位符替换
        doProcessProperties(beanFactoryToProcess, valueResolver);
    }

    /**
     * Return the property sources that were actually applied during
     */
    public PropertySources getAppliedPropertySources() throws IllegalStateException {
        Assert.state(this.appliedPropertySources != null, "PropertySources have not yet been applied");
        return this.appliedPropertySources;
    }
}