Spring源碼解析(十七)-PropertySourcesPlaceholderConfigurer

Spring版本

5.2.5.RELEASE

參考

《芋道源碼》

在平時(shí)的項(xiàng)目中,我們經(jīng)常會(huì)使用配置文件來(lái)根據(jù)不同的環(huán)境動(dòng)態(tài)加載配置項(xiàng)。那么,spring是如何使用配置項(xiàng)替換掉bean中的占位符的呢?這一切的神奇之處都在PropertySourcesPlaceholderConfigurer(5.2之前是使用PropertyPlaceholderConfigurer,5.2廢棄該類,官方建議使用PropertySourcesPlaceholderConfigurer),它負(fù)責(zé)加載并替換bean中的占位符。首先從一個(gè)小demo來(lái)認(rèn)識(shí)這個(gè)類。

1. DEMO

1.1 Student

public class Student {

    private String id;

    private String name;

    private String desc;

   // 省略 getter、setter
}

1.2 CustomPropertyConfig

public class CustomPropertyConfig extends PropertySourcesPlaceholderConfigurer {

    private Resource [] locations;

    private PropertiesPersister propertiesPersister = new DefaultPropertiesPersister();

    @Override
    public void setLocations(Resource... locations) {
        this.locations = locations;
    }

    @Override
    public void setLocalOverride(boolean localOverride) {
        this.localOverride = localOverride;
    }

    @Override
    protected void loadProperties(Properties props) throws IOException {
        if (this.locations != null) {
            for (Resource location : this.locations) {
                InputStream inputStream = null;
                try {
                    String fileName = location.getFilename();
                    String env = "application-" + System.getProperty("spring.profiles.active", "dev") + ".properties";
                    if (fileName.contains(env)) {
                        logger.info("loading properties file from " + location);
                        inputStream = location.getInputStream();
                        this.propertiesPersister.load(props,inputStream);
                    }
                } catch (Exception e) {
                    logger.info("error",e);
                } finally {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                }
            }
        }
    }
}

1.3 環(huán)境配置文件

application-dev.properties

student.name=student-dev

application-test.properties

student.name=student-test
student.value=student-test-value

1.4 spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="propertyConfig" class="com.kungyu.custom.element.CustomPropertyConfig">
        <property name="locations">
            <list>
                <value>classpath:application-dev.properties</value>
                <value>classpath:application-test.properties</value>
            </list>
        </property>
    </bean>
    <bean id="student" class="com.kungyu.custom.element.Student">
        <property name="name" value="${student.name}"/>
    </bean>
</bean>

1.5 測(cè)試

  public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        Student student = (Student) context.getBean("student");
        System.out.println(student.getName());
}

啟動(dòng)參數(shù)配置如下:


啟動(dòng)參數(shù)配置

用于指定加載的配置文件,輸出如下:


輸出結(jié)果

可以看到,student這個(gè)bean中name屬性原本使用的是${student.name}這個(gè)占位符,如今已經(jīng)被替換成配置文件application-dev.properties中的student-name的屬性值student-dev

當(dāng)加載測(cè)試環(huán)境配置的時(shí)候,修改VM options即可

2. 源碼解讀

通過(guò)debug,我們可以發(fā)現(xiàn),入口在于PropertySourcesPlaceholderConfigurer#postProcessBeanFactory

找到該入口方法的debug方法可以查看《Spring源碼解析(十六)-BeanFactoryPostProcessor》

2.1 PropertySourcesPlaceholderConfigurer#postProcessBeanFactory

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // propertySources:屬性資源,內(nèi)部包含一個(gè)propertySourceList,用于存放各個(gè)環(huán)境下的資源
        if (this.propertySources == null) {
            this.propertySources = new MutablePropertySources();
            // environment系統(tǒng)環(huán)境的一些配置
            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);
                        }
                    }
                );
            }
            try {
                // 通過(guò)mergeProperties加載用戶配置的資源文件
                PropertySource<?> localPropertySource =
                        new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
                // localOverride為true代表優(yōu)先加載本地資源,也就是優(yōu)先級(jí)高(所以使用addFirst),false代表最后加載本地資源,也就是本地資源優(yōu)先級(jí)地(所以使用addLast)
                // 這種優(yōu)先級(jí)的高低在PropertySourcesPropertyResolver#getProperty中體現(xiàn)
                if (this.localOverride) {
                    this.propertySources.addFirst(localPropertySource);
                }
                else {
                    this.propertySources.addLast(localPropertySource);
                }
            }
            catch (IOException ex) {
                throw new BeanInitializationException("Could not load properties", ex);
            }
        }

        // 進(jìn)行資源解析,同時(shí)替換${}占位符
        processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
        this.appliedPropertySources = this.propertySources;
    }

首先判斷類屬性propertySources是否為空,為空則構(gòu)造一個(gè)對(duì)象,接著主要分倆步:

  • 加載系統(tǒng)環(huán)境配置,加入到propertySources
  • 加載用戶的配置,也就是DEMO中的配置文件,同樣加入到propertySources

這里需要留意多份環(huán)境配置文件之間存在優(yōu)先級(jí)之分,通過(guò)addFirst加入的配置文件優(yōu)先級(jí)最高:

    /**
     * Add the given property source object with highest precedence.
     */
    public void addFirst(PropertySource<?> propertySource) {
        removeIfPresent(propertySource);
        this.propertySourceList.add(0, propertySource);
    }

通過(guò)addLast加入的優(yōu)先級(jí)最低:

    /**
     * Add the given property source object with lowest precedence.
     */
    public void addLast(PropertySource<?> propertySource) {
        removeIfPresent(propertySource);
        this.propertySourceList.add(propertySource);
    }

這個(gè)優(yōu)先級(jí)的區(qū)分,將體現(xiàn)在2.12節(jié)的getProperty方法中

處理完配置文件之后,調(diào)用processProperties方法正式開(kāi)始占位符相關(guān)處理

2.2 PropertySourcesPlaceholderConfigurer#processProperties

    protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
            final ConfigurablePropertyResolver propertyResolver) throws BeansException {

        // 設(shè)置解析器的占位符前綴,占位符后綴,和默認(rèn)值分割符,對(duì)應(yīng)值如下
        // placeholderPrefix   ${
        // placeholderSuffix   }
        // valueSeparator  :
        propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
        propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
        propertyResolver.setValueSeparator(this.valueSeparator);

        // 構(gòu)造一個(gè)函數(shù)式接口的解析器
        StringValueResolver valueResolver = strVal -> {
            // ignoreUnresolvablePlaceholders 是否無(wú)視不可解析的占位符,如果設(shè)置為false,那么碰到不可解析的占位符的時(shí)候,會(huì)拋出異常
            String resolved = (this.ignoreUnresolvablePlaceholders ?
                    propertyResolver.resolvePlaceholders(strVal) :
                    propertyResolver.resolveRequiredPlaceholders(strVal));
            if (this.trimValues) {
                resolved = resolved.trim();
            }
            return (resolved.equals(this.nullValue) ? null : resolved);
        };

        // 真正解析占位符
        doProcessProperties(beanFactoryToProcess, valueResolver);
    }

邏輯分為三步:

  • 設(shè)置占位符前綴、后綴和默認(rèn)值分隔符
propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
propertyResolver.setValueSeparator(this.valueSeparator);
  • 構(gòu)造占位符解析器的函數(shù)式接口
StringValueResolver valueResolver = strVal - > {
    /* ignoreUnresolvablePlaceholders 是否無(wú)視不可解析的占位符,如果設(shè)置為false,那么碰到不可解析的占位符的時(shí)候,會(huì)拋出異常 */
    String resolved = (this.ignoreUnresolvablePlaceholders ?
               propertyResolver.resolvePlaceholders( strVal ) :
               propertyResolver.resolveRequiredPlaceholders( strVal ) );
    if ( this.trimValues )
    {
        resolved = resolved.trim();
    }
    return(resolved.equals( this.nullValue ) ? null : resolved);
};

該函數(shù)式接口在2.7節(jié)中將會(huì)被使用,通過(guò)resolvePlaceholdersresolveRequiredPlaceholders對(duì)占位符strVal進(jìn)行解析

  • 真正開(kāi)始解析占位符
doProcessProperties(beanFactoryToProcess, valueResolver);

2.3 PropertySourcesPlaceholderConfigurer#doProcessProperties

protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess, StringValueResolver valueResolver) {
    // 傳入valueResolver構(gòu)造一個(gè)vistor,之后將使用valueResolver來(lái)解析beanDefinition中的占位符屬性
    BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
    // 獲取全部beanName
    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.
        // curName.equals(this.beanName):占位符解析本質(zhì)上也是注冊(cè)了一個(gè)bean,所以對(duì)于占位符這個(gè)bean,需要跳過(guò)
        if (! (curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
            // 獲取BeanDefinition
            BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
            try {
                // 對(duì)parentName、class、property等等進(jìn)行解析,并且替換beanDefinition中原來(lái)的占位符
                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.
    // 對(duì)別名也應(yīng)用解析起進(jìn)行處理
    beanFactoryToProcess.resolveAliases(valueResolver);
    // New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
    // 往嵌入式注解解析器列表增加該解析器,在創(chuàng)建bean的過(guò)程中,會(huì)使用該解析器去解析@Value的屬性
    beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}
  • 將解析器valueResolver作為參數(shù)構(gòu)造了一個(gè)BeanDefinitionVisitor對(duì)象,那么可以想象,解析的動(dòng)作應(yīng)該是發(fā)生在BeanDefinitionVisitor對(duì)象的成員方法中了
  • 獲取全部已解析的BeanDefinition,遍歷,使用BeanDefinitionVisitor對(duì)象的成員方法visitBeanDefinition進(jìn)行解析并且替換BeanDefinition中的占位符
  • 調(diào)用resolveAliases處理別名,不是我們關(guān)心的流程,具體不展開(kāi)了
  • 調(diào)用addEmbeddedValueResolver,往嵌入式注解解析器列表embeddedValueResolvers增加該解析器,在《Spring源碼解析(十)-填充bean屬性》4.2節(jié)中解析了@Value注解,那時(shí)候會(huì)使用到該解析器列表embeddedValueResolvers
    @Override
    public void addEmbeddedValueResolver(StringValueResolver valueResolver) {
        Assert.notNull(valueResolver, "StringValueResolver must not be null");
        this.embeddedValueResolvers.add(valueResolver);
    }

2.4 BeanDefinitionVisitor#visitBeanDefinition

    public void visitBeanDefinition(BeanDefinition beanDefinition) {
        visitParentName(beanDefinition);
        visitBeanClassName(beanDefinition);
        visitFactoryBeanName(beanDefinition);
        visitFactoryMethodName(beanDefinition);
        visitScope(beanDefinition);
        if (beanDefinition.hasPropertyValues()) {
            visitPropertyValues(beanDefinition.getPropertyValues());
        }
        if (beanDefinition.hasConstructorArgumentValues()) {
            ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
            visitIndexedArgumentValues(cas.getIndexedArgumentValues());
            visitGenericArgumentValues(cas.getGenericArgumentValues());
        }
    }

可以看到,該方法對(duì)parentName、beanClass等數(shù)據(jù)域都進(jìn)行了解析,這里我們只關(guān)心屬性的解析方法visitPropertyValues

2.5 BeanDefinitionVisitor#visitPropertyValues

    protected void visitPropertyValues(MutablePropertyValues pvs) {
        PropertyValue[] pvArray = pvs.getPropertyValues();
        for (PropertyValue pv : pvArray) {
            Object newVal = resolveValue(pv.getValue());
            if (!ObjectUtils.nullSafeEquals(newVal, pv.getValue())) {
                pvs.add(pv.getName(), newVal);
            }
        }
    }
  • 拿到屬性列表pvArray
  • 遍歷pvArray,調(diào)用resolveValue進(jìn)行占位符解析,如果解析后的新值和原值不同,通過(guò)add方法進(jìn)行替換或者合并(如果可以合并的前提下)

2.6 BeanDefinitionVisitor#resolveValue

    @Nullable
    protected Object resolveValue(@Nullable Object value) {
        if (value instanceof BeanDefinition) {
            visitBeanDefinition((BeanDefinition) value);
        }
        else if (value instanceof BeanDefinitionHolder) {
            visitBeanDefinition(((BeanDefinitionHolder) value).getBeanDefinition());
        }
        else if (value instanceof RuntimeBeanReference) {
            RuntimeBeanReference ref = (RuntimeBeanReference) value;
            String newBeanName = resolveStringValue(ref.getBeanName());
            if (newBeanName == null) {
                return null;
            }
            if (!newBeanName.equals(ref.getBeanName())) {
                return new RuntimeBeanReference(newBeanName);
            }
        }
        else if (value instanceof RuntimeBeanNameReference) {
            RuntimeBeanNameReference ref = (RuntimeBeanNameReference) value;
            String newBeanName = resolveStringValue(ref.getBeanName());
            if (newBeanName == null) {
                return null;
            }
            if (!newBeanName.equals(ref.getBeanName())) {
                return new RuntimeBeanNameReference(newBeanName);
            }
        }
        else if (value instanceof Object[]) {
            visitArray((Object[]) value);
        }
        else if (value instanceof List) {
            visitList((List) value);
        }
        else if (value instanceof Set) {
            visitSet((Set) value);
        }
        else if (value instanceof Map) {
            visitMap((Map) value);
        }
        else if (value instanceof TypedStringValue) {
            TypedStringValue typedStringValue = (TypedStringValue) value;
            String stringValue = typedStringValue.getValue();
            if (stringValue != null) {
                String visitedString = resolveStringValue(stringValue);
                typedStringValue.setValue(visitedString);
            }
        }
        else if (value instanceof String) {
            return resolveStringValue((String) value);
        }
        return value;
    }

這里對(duì)占位符名稱value做了各種數(shù)據(jù)類型的處理,一般傳入的占位符名稱都是string類型的,因此直接跳到resolveStringValue((String) value)

2.7 BeanDefinitionVisitor#resolveStringValue

    @Nullable
    protected String resolveStringValue(String strVal) {
        if (this.valueResolver == null) {
            throw new IllegalStateException("No StringValueResolver specified - pass a resolver " +
                    "object into the constructor or override the 'resolveStringValue' method");
        }
        String resolvedValue = this.valueResolver.resolveStringValue(strVal);
        // Return original String if not modified.
        return (strVal.equals(resolvedValue) ? strVal : resolvedValue);
    }

這里使用了valueResolver.resolveStringValue(strVal)進(jìn)行解析,而追蹤下來(lái),我們知道valueResolver其實(shí)就是2.2節(jié)中構(gòu)造的解析器:

StringValueResolver valueResolver = strVal - > {
    /* ignoreUnresolvablePlaceholders 是否無(wú)視不可解析的占位符,如果設(shè)置為false,那么碰到不可解析的占位符的時(shí)候,會(huì)拋出異常 */
    String resolved = (this.ignoreUnresolvablePlaceholders ?
               propertyResolver.resolvePlaceholders( strVal ) :
               propertyResolver.resolveRequiredPlaceholders( strVal ) );
    if ( this.trimValues )
    {
        resolved = resolved.trim();
    }
    return(resolved.equals( this.nullValue ) ? null : resolved);
};

那么,明顯,resolveStringValue方法也就是調(diào)用resolvePlaceholdersresolveRequiredPlaceholders中的其中一個(gè),我們以更為嚴(yán)格的resolveRequiredPlaceholders為例進(jìn)行解析

2.8 AbstractPropertyResolver#resolveRequiredPlaceholders

    @Override
    public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
        if (this.strictHelper == null) {
            this.strictHelper = createPlaceholderHelper(false);
        }
        return doResolvePlaceholders(text, this.strictHelper);
    }

可以看到是交由doResolvePlaceholders去進(jìn)行解析的

2.9 AbstractPropertyResolver#doResolvePlaceholders

    private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
        // 注意,這里的形參PlaceholderResolver是一個(gè)函數(shù)式接口,這里傳入getPropertyAsRawString方法作為PlaceholderResolver#resolvePlaceholder的實(shí)現(xiàn)
        return helper.replacePlaceholders(text, this::getPropertyAsRawString);
    }

replacePlaceholders方法的第二個(gè)參數(shù)是函數(shù)式接口參數(shù)PlaceholderResolver

    @FunctionalInterface
    public interface PlaceholderResolver {

        /**
         * Resolve the supplied placeholder name to the replacement value.
         * @param placeholderName the name of the placeholder to resolve
         * @return the replacement value, or {@code null} if no replacement is to be made
         */
        @Nullable
        String resolvePlaceholder(String placeholderName);
    }

所以,getPropertyAsRawString便是該函數(shù)式接口中resolvePlaceholder的實(shí)現(xiàn)方法,而resolvePlaceholder將會(huì)在2.11節(jié)中被調(diào)用

2.10 PropertyPlaceholderHelper#replacePlaceholders

    public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
        Assert.notNull(value, "'value' must not be null");
        return parseStringValue(value, placeholderResolver, null);
    }

2.11 PropertyPlaceholderHelper#parseStringValue

    protected String parseStringValue(
            String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {

        // value:傳入的原始占位符
        // placeholderResolver:解析器
        // visitedPlaceholders:緩存集合
        int startIndex = value.indexOf(this.placeholderPrefix);
        if (startIndex == -1) {
            return value;
        }

        // spring允許存在多個(gè)占位符,如student.name可以配置為${student.name}${student.value}
        // 所以下面的代碼需要循環(huán)處理多個(gè)占位符,并對(duì)result進(jìn)行替換
        StringBuilder result = new StringBuilder(value);
        while (startIndex != -1) {
            // 查找結(jié)束符的位置
            int endIndex = findPlaceholderEndIndex(result, startIndex);
            if (endIndex != -1) {
                // 進(jìn)行截取,去除占位符,得到真正的屬性名稱,比如${student.name} -> student.name
                String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
                String originalPlaceholder = placeholder;
                if (visitedPlaceholders == null) {
                    visitedPlaceholders = new HashSet<>(4);
                }
                if (!visitedPlaceholders.add(originalPlaceholder)) {
                    throw new IllegalArgumentException(
                            "Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
                }
                // Recursive invocation, parsing placeholders contained in the placeholder key.
                // 遞歸處理占位符中嵌套占位符
                placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
                // Now obtain the value for the fully resolved key...
                // 通過(guò)解析器獲取到占位符對(duì)應(yīng)的值
                String propVal = placeholderResolver.resolvePlaceholder(placeholder);
                if (propVal == null && this.valueSeparator != null) {
                    // 獲取不到值,說(shuō)明此時(shí)placeholder可能具備默認(rèn)值
                    // 查找默認(rèn)值劃分符號(hào)
                    int separatorIndex = placeholder.indexOf(this.valueSeparator);
                    if (separatorIndex != -1) {
                        // 查找到劃分符號(hào),截取獲取屬性名稱
                        String actualPlaceholder = placeholder.substring(0, separatorIndex);
                        // 截取默認(rèn)值
                        String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
                        // 嘗試解析
                        propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
                        if (propVal == null) {
                            // 解析不到,使用默認(rèn)值
                            propVal = defaultValue;
                        }
                    }
                }
                if (propVal != null) {
                    // Recursive invocation, parsing placeholders contained in the
                    // previously resolved placeholder value.
                    // 解析屬性值
                    propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
                    // 替換結(jié)果中的占位符為屬性值
                    result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
                    if (logger.isTraceEnabled()) {
                        logger.trace("Resolved placeholder '" + placeholder + "'");
                    }
                    startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
                }
                else if (this.ignoreUnresolvablePlaceholders) {
                    // Proceed with unprocessed value.
                    startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
                }
                else {
                    throw new IllegalArgumentException("Could not resolve placeholder '" +
                            placeholder + "'" + " in value \"" + value + "\"");
                }
                visitedPlaceholders.remove(originalPlaceholder);
            }
            else {
                startIndex = -1;
            }
        }
        return result.toString();
    }

方法看著很長(zhǎng),但邏輯其實(shí)挺好理解的,主要就是解決占位符嵌套和多個(gè)占位符這倆種情況,具體邏輯細(xì)看一下即可明白,不展開(kāi)了,需要留意的是:

String propVal = placeholderResolver.resolvePlaceholder(placeholder);

這里的resolvePlaceholder實(shí)際上調(diào)用的是2.9節(jié)中的getPropertyAsRawString方法

多個(gè)占位符的情況

占位符內(nèi)部嵌套的情況

2.12 PropertySourcesPropertyResolver#getPropertyAsRawString

    @Nullable
    protected String getPropertyAsRawString(String key) {
        return getProperty(key, String.class, false);
    }

    @Nullable
    protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
        if (this.propertySources != null) {
            // 遍歷,從各個(gè)propertySource中查找值,找到值之后馬上返回,拋棄后面的propertySource
            for (PropertySource<?> propertySource : this.propertySources) {
                // 省略
                Object value = propertySource.getProperty(key);
                if (value != null) {
                    if (resolveNestedPlaceholders && value instanceof String) {
                        value = resolveNestedPlaceholders((String) value);
                    }
                    logKeyFound(key, propertySource, value);
                    // 類型轉(zhuǎn)化
                    return convertValueIfNecessary(value, targetValueType);
                }
            }
        }
        // 省略
        return null;
    }

核心邏輯就一句:

Object value = propertySource.getProperty(key);

propertySource取出元素值,不為空的話,直接返回,這也是2.1節(jié)中優(yōu)先級(jí)的代碼體現(xiàn)

3. 總結(jié)

配置文件的運(yùn)作流程整個(gè)邏輯代碼是挺好理解的,并不復(fù)雜,稍微花點(diǎn)心,就看懂了,邏輯總結(jié)起來(lái)不外乎幾步:

  • 獲取配置文件,轉(zhuǎn)化為propertySources
  • 構(gòu)造基于propertySources的解析器valueResolver
  • 獲取BeanDefinitions,遍歷,并對(duì)占位符逐一解析并替換

只不過(guò)在其中還穿插了比如占位符嵌套、值轉(zhuǎn)化等場(chǎng)景的處理。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容