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;
        }
    }

总结

 

原文地址:https://www.cnblogs.com/honger/p/6815181.html