spring源碼分析之配置文件名占位符的解析(一)


一、直接寫個測試例子

package com.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.test.controller.User;

public class UserTest {
    
    
    
    @Test
    public void test() {
        
        @SuppressWarnings("resource")
        ApplicationContext beanFactory = new ClassPathXmlApplicationContext("classpath:springContext.xml");
        
        User user = beanFactory.getBean("user", User.class);
        
        String username = user.getName();
        
        
        System.out.println(username);
        
        
    }
    

}

二、直接debug運行

 

在進入代碼之前,先了解一下這個ClassPathXmlApplicationContext類的繼承關系

 

 

1、首先進入

 

//這里的configLocation的值就是classpath:springContext.xml
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
        this(new String[] {configLocation}, true, null);
    }

 

2、繼續進入ClassPathXmlApplicationContext的構造器

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
            throws BeansException {

        super(parent);//parent為null值
        setConfigLocations(configLocations);
        if (refresh) {
            refresh();
        }
    }

3、進入setConfigLocation這個方法定義於父類AbstractRefreshableConfigApplicationContext

public void setConfigLocations(String... locations) {
        if (locations != null) {
            Assert.noNullElements(locations, "Config locations must not be null");
            //new一個String數組,用來裝解析后的配置文件地址
            this.configLocations = new String[locations.length];
            for (int i = 0; i < locations.length; i++) {
                this.configLocations[i] = resolvePath(locations[i]).trim();
            }
        }
        else {
            this.configLocations = null;
        }
    }

4、繼續進入resolvePath方法,它這里創建了一個StandEnvironment實例,這個實例包含着一些系統參數,環境變量參數,不過它現在還沒有做任何事情,僅僅是創建它的一個實例。后面用到再說。

 

 

protected String resolvePath(String path) {
        return getEnvironment().resolveRequiredPlaceholders(path);
    }

5.進入getEnvironment方法,這個方法定義於父類AbstractApplicationContext

 

@Override
    public ConfigurableEnvironment getEnvironment() {
        if (this.environment == null) {
            this.environment = createEnvironment();
        }
        return this.environment;
    }

 

6、接下來調用StandEnvironmentresolveRequiredPlaceholders(path)方法,先看看StrandEnvironment結構。

這里的resolveRequiredPlaceholders方法定義在父類AbstractEnvironment中,這個方法的代碼如下:

//注意這個類的實例被注入到了PropertySourcesPropertyResolver中了,等下會用到
private final MutablePropertySources propertySources = new                   MutablePropertySources(this.logger);

private final ConfigurablePropertyResolver propertyResolver =
            new PropertySourcesPropertyResolver(this.propertySources);

public AbstractEnvironment() {
        customizePropertySources(this.propertySources);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug(format(
                    "Initialized %s with PropertySources %s", getClass().getSimpleName(), this.propertySources));
        }
    }

@Override//這個方法是實現於StandardEnvironment類中,我把它放到了一起
    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties())); //這個getSystemProperties調用的是System.getProperties()
        propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));//獲得系統變量

    }

@Override
    public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
       // 這個propertyResolver在new出StandardEnvironment的時候就被創建了
        return this.propertyResolver.resolveRequiredPlaceholders(text);
    }

7、進入PropertySourcesPropertyResolver 類的resolveRequiredPlaceholders方法,這個方法存在於PropertySourcesPropertyResolver的父類AbstractPropertyResolver里面

@Override
    public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
        if (this.strictHelper == null) {
            //這里創建了一個占位符助手實例,這個false參數表示忽略不能解析的占位符
            this.strictHelper = createPlaceholderHelper(false);
        }
         //這里才是真正解析的開始
        return doResolvePlaceholders(text, this.strictHelper);
    }

8、這個PlaceholderHelper在創建時有一段靜態塊的初始化

 

private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<String, String>(4);

    static {
        wellKnownSimplePrefixes.put("}", "{");
        wellKnownSimplePrefixes.put("]", "[");
        wellKnownSimplePrefixes.put(")", "(");
    }

 

9、進入doResolvePlaceholders方法

private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
        //調用了屬性占位符助手的替換占位符的方法
        return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() {
            @Override
            public String resolvePlaceholder(String placeholderName) {
                return getPropertyAsRawString(placeholderName);
            }
        });
    }

10、進到PropertyPlaceholderHelper類的replacePlaceholders方法,strVal就是我們從前面傳進來的配置文件的名稱:classpath:springContext.xmlplaceholderResolver可以通過占位符找到對應的值,怎么找的,后面再說。這段代碼非常有用,也許有一天會對你有用。

protected String parseStringValue(
            String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {

        StringBuilder result = new StringBuilder(strVal);
        //檢查這個字符串時候有 ${ 前綴
        int startIndex = strVal.indexOf(this.placeholderPrefix);
        while (startIndex != -1) {
            //如果有 ${ 前綴,再檢查是否有 } 后綴
            int endIndex = findPlaceholderEndIndex(result, startIndex);
            if (endIndex != -1) {
                //拿到占位符,如classpath:spring${key}.xml,這個占位符是key
                String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
                String originalPlaceholder = placeholder;
                //將當前的占位符存到set集合中,如果set集合有了,就會添加失敗
                //就會報錯,循環引用錯誤,比如${a},這個a的值依然是${a}
                 //這樣就陷入了無限解析了,根本停不下來
                if (!visitedPlaceholders.add(originalPlaceholder)) {
                    throw new IllegalArgumentException(
                            "Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
                }
                // Recursive invocation, parsing placeholders contained in the placeholder key.             //對占位符進行解析,如:${${a}},所以要繼續解析
                placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
                // Now obtain the value for the fully resolved key...
         //調用這個解析器查找占位符對應的值,這個方法的代碼在下面11步給出
                String propVal = placeholderResolver.resolvePlaceholder(placeholder);
                if (propVal == null && this.valueSeparator != null) {
                     //如果為null,那么查找這個propVal是否為:分割的字符串
                    int separatorIndex = placeholder.indexOf(this.valueSeparator);
                    if (separatorIndex != -1) {
                   //如果propVal為key:Context,那么這個值應為key
                        String actualPlaceholder = placeholder.substring(0, separatorIndex);
                     //如果propVal為key:Context,那么就是Context
                        String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
                     //跟上面的一樣去系統屬性中查找
                        propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
                       //如果為空,那么就設置為defaultValue,如key:Context
                       //defaultValue = Context;
                        if (propVal == null) {
                            propVal = defaultValue;
                        }
                    }
                }
                if (propVal != null) {
                    // Recursive invocation, parsing placeholders contained in the
                    // previously resolved placeholder value.
                    //這個值可能也有站位符,繼續遞歸解析
                    propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
                   //得到了占位符對應的值后替換掉占位符
                    result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
                    if (logger.isTraceEnabled()) {
                        logger.trace("Resolved placeholder '" + placeholder + "'");
                    }
                   //繼續查找是否還有后續的占位符
                    startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
                }
                //如果propValue為null,那么就說明這個占位符沒有值,如果設置為忽略
                //不能解析的占位符,那么繼續后續的占位符,否則報錯
                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 string value \"" + strVal + "\"");
                } 
                //解析成功就刪除set集合中對應的占位符
                visitedPlaceholders.remove(originalPlaceholder);
            }
            else {
                startIndex = -1;
            }
        }

        return result.toString();
    }

11resolvePlaceholder方法調用了getPropertyAsRawString方法,這個方法又調用了PropertySourcesPropertyResolver類的getProperty方法

//參數說明,key是傳進來的占位符,targetValueType指的是目標類型,這里肯定是String.class, resolveNestedPlaceholders表示是否要對嵌套的占位符進行解析,這里傳的是false,不需要。
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
        boolean debugEnabled = logger.isDebugEnabled();
        if (logger.isTraceEnabled()) {
            logger.trace(String.format("getProperty(\"%s\", %s)", key, targetValueType.getSimpleName()));
        }
        //這個類就是在AstractEnvironment中傳進來的MutablePropertySource 實例,上面第6點已經說它是怎么進來的,它存有系統屬性和系統環境變量
        if (this.propertySources != null) {
            for (PropertySource<?> propertySource : this.propertySources) {
                if (debugEnabled) {
                    logger.debug(String.format("Searching for key '%s' in [%s]", key, propertySource.getName()));
                }
                //從系統屬性中尋找值
                Object value = propertySource.getProperty(key);
                if (value != null) {
                    Class<?> valueType = value.getClass();
                    //如果允許解析嵌入的${},並且是String類型的就繼續解析
                    if (resolveNestedPlaceholders && value instanceof String) {
                        value = resolveNestedPlaceholders((String) value);
                    }
                    if (debugEnabled) {
                        logger.debug(String.format("Found key '%s' in [%s] with type [%s] and value '%s'",
                                key, propertySource.getName(), valueType.getSimpleName(), value));
                    }
                    //判斷當前值得類型能夠轉換成目標類型,如果不能,直接報錯
                    if (!this.conversionService.canConvert(valueType, targetValueType)) {
                        throw new IllegalArgumentException(String.format(
                                "Cannot convert value [%s] from source type [%s] to target type [%s]",
                                value, valueType.getSimpleName(), targetValueType.getSimpleName()));
                    }
                    //如果可以轉換直接轉換返回這個值
                    return this.conversionService.convert(value, targetValueType);
                }
            }
        }
        if (debugEnabled) {
            logger.debug(String.format("Could not find key '%s' in any property source. Returning [null]", key));
        }
       //如果在系統屬性中沒有得到值,那么返回null值。
        return null;
    }

12、最后返回到AbstractRefreshableConfigApplicationContext類,將解析后的配置文件路徑設置到configLocations屬性中

 

public void setConfigLocations(String... locations) {
        if (locations != null) {
            Assert.noNullElements(locations, "Config locations must not be null");
            this.configLocations = new String[locations.length];
            for (int i = 0; i < locations.length; i++) {
                this.configLocations[i] = resolvePath(locations[i]).trim();
            }
        }
        else {
            this.configLocations = null;
        }
    }

 

總結

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM