zoukankan      html  css  js  c++  java
  • 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;
            }
        }

    总结

     

  • 相关阅读:
    Unity中将相机截图保存本地后颜色变暗的解决方法
    《无间道》亚索盲僧上演天台对决——开发者日志(二)_为人物添加动画
    《无间道》亚索盲僧上演天台对决——开发者日志(一)_项目启动
    CCF推荐期刊会议列表(2019第五版)——《中国计算机学会推荐国际学术会议和期刊目录》
    用3dMax给lol人物模型制作表情动画并导入Unity
    与100个诺手一起跨年
    C#发邮件
    SQL的各种连接Join详解
    Oracle&SQLServer中实现跨库查询
    SQL SERVER/ORACLE连接查询更简洁方便的方式
  • 原文地址:https://www.cnblogs.com/honger/p/6815181.html
Copyright © 2011-2022 走看看