当前位置:首页 > 开发 > 编程语言 > Spring > 正文

Spring源码学习-PropertyPlaceholderHelper

发表于: 2014-04-25   作者:bylijinnan   来源:转载   浏览:
摘要: 今天在看Spring 3.0.0.RELEASE的源码,发现PropertyPlaceholderHelper的一个bug 当时觉得奇怪,上网一搜,果然是个bug,不过早就有人发现了,且已经修复: 详见: http://forum.spring.io/forum/spring-projects/container/88107-propertyplaceholderhelper-bug

今天在看Spring 3.0.0.RELEASE的源码,发现PropertyPlaceholderHelper的一个bug
当时觉得奇怪,上网一搜,果然是个bug,不过早就有人发现了,且已经修复:
详见: http://forum.spring.io/forum/spring-projects/container/88107-propertyplaceholderhelper-bug

PropertyPlaceholderHelper的另一个Bug( SPR-5369)就没那么好理解了,后来还是在测试类PropertyPlaceholderHelperTests.java中找到例子,
一步一步跟踪,总算明白是怎么回事了。详见: https://github.com/spring-projects/spring-framework/pull/42/files

下面是简化版的PropertyPlaceholderHelper,原理跟Spring的一样,加入了一些我自己的理解


import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.springframework.util.StringUtils;

/**
 * @author lijinnan
 * @date:2014-4-25
 */
public class PropertyPlaceholderHelper {

    
    public static final String PREFIX = "${";
    public static final String SUFFIX = "}";
    
    private static String parseStringValue(String strVal, Set<String> set) {
        StringBuilder buf = new StringBuilder(strVal);
        int startIndex = buf.indexOf(PREFIX);
        
        //递归的出口:没有变量需要替换了
        if (startIndex < 0) {
            return buf.toString();
        }
        
        while (true) {
            int endIndex = findPlaceholderEndIndex(buf, startIndex);
            if (endIndex == -1) {
                break;
            }
            
            //找到了最外层的变量,例如"${x${abc}x}"里面的"x${abc}x"--显然,变量里还有变量;这就是为什么要用递归
            String placeHolder = buf.substring(startIndex + PREFIX.length(), endIndex);
            
            //确保后面移出的变量是我们放进去的变量(而不是解析过程中产生的、新的变量,见求解"${top}"的例子)
            String originPlaceHolder = placeHolder;
            
            //防止循环定义:例如strVal="${a}",而valueMap=[a="${b}", b="${a}"]
            if (!set.add(originPlaceHolder)) {
                throw new IllegalArgumentException("circular placeholder");
            }
            
            //递归对"变量里的变量"求解,直到最里面的变量求解得出结果为止,再一层一层的向外层返回
            placeHolder = parseStringValue(placeHolder, set);
            
            //可能value里面还有变量,需要递归求解
            String val = parseStringValue(valueMap.get(placeHolder), set);
            
            if (val != null) {
                buf.replace(startIndex, endIndex + SUFFIX.length(), val);
                
                //继续向后替换变量:"${a}xx${b}",替换好${a}之后,继续替换${b}
                startIndex = buf.indexOf(PREFIX, startIndex + val.length());
            }
            set.remove(originPlaceHolder);  //注意这里
            //set.remove(placeHolder);  //bug!
        }
        
        return buf.toString();
        
    }

    /**
     * 查找与PREFIX配对的SUFFIX
     * 注意处理嵌套的情况:用within变量来记录
     * 例如${ab${cd}},startIndex指向a,从a开始找,当找到"${"时,within为1;
     * 找到第一个"}"时,within减1,抵消前面的"${";while循环继续,直到找到最后的"}"
     * 这有点像"利用栈来判断括号是否配对"
     */
    private static int findPlaceholderEndIndex(StringBuilder buf, int startIndex) {
        int within = 0;
        int index = startIndex + PREFIX.length();
        while (index < buf.length()) {
            
            //发现了一个嵌套的PREFIX
            if (StringUtils.substringMatch(buf, index, PREFIX)) {
                within++;
                index = index + PREFIX.length();
             
            //发现了一个嵌套的SUFFIX,因此抵消一个PREFIX:within减1
            } else if (StringUtils.substringMatch(buf, index, SUFFIX)) {
                if (within > 0){
                    within--;
                    index = index + SUFFIX.length();
                } else if (within == 0){
                    return index;
                }
            } else {
                index++;
            }
        }
        return -1;
    }
    
    //for test
    private static Map<String, String> valueMap = new HashMap<String, String>();
    static {
        valueMap.put("inner", "ar");
        valueMap.put("bar", "ljn");
        
        //求解"${top}"的过程中,第一次递归返回时,${differentiator}被替换为"first",然后产生
        //了一个新的placeHolder="first.grandchild",这个placeHolder用"actualValue"替换
        //然后移除变量,如果set.remove(placeHolder)的话,那移除的就是"first.grandchild"
        //而实际上,它应该移除的是"${differentiator}.grandchild"。这个bug难以发现
        valueMap.put("top", "${child}+${child}");
        valueMap.put("child", "${${differentiator}.grandchild}");
        valueMap.put("differentiator", "first");
        valueMap.put("first.grandchild", "actualValue");
    }

    public static void main(String[] args) {
        String s ="${top}";
        System.out.println(parseStringValue(s, new HashSet<String>())); //actualValue+actualValue
        
        s = "foo=${b${inner}}-${bar}-end";
        System.out.println(parseStringValue(s, new HashSet<String>())); //foo=ljn-ljn-end
        
    }

}


Spring源码学习-PropertyPlaceholderHelper

  • 0

    开心

    开心

  • 0

    板砖

    板砖

  • 0

    感动

    感动

  • 0

    有用

    有用

  • 0

    疑问

    疑问

  • 0

    难过

    难过

  • 0

    无聊

    无聊

  • 0

    震惊

    震惊

编辑推荐
a) Spring 初始化的时候首先要运行的类为: org.springframework.web.context. ContextLoaderListen
最近在看spring的源码,担心忘掉了,打个记号,也请大家一起指正其中的错误,防止走歪路。 从xml配
这一段我主要去看看spring容器的创建,继续(一)的代码,我们看 if (this.context == null) { this
众所周知,Spring以其强大而又灵活的IoC管理功能著称。IoC本质上是通过依赖注入DI的方式实现组件之间
转自 http://www.iteye.com/magazines/72 本文主要以 spring ioc 容器基本代码骨架为切入点,理解 i
a) Spring 初始化的时候首先要运行的类为: org.springframework.web.context. ContextLoaderListen
众所周知,Spring以其强大而又灵活的IoC管理功能著称。IoC本质上是通过依赖注入DI的方式实现组件之间
一直想抽空把Spring源码拿来读读,但真正去做这件事的时候发现不简单,Spring发展这么多年,它的规
Spring发展这么多年,它的规模已不是一个一般的开源框架所能比的,它的主要架构和流程不是非常清晰,
AOP有些特有的概念,如:advisor、advice和pointcut等等,使用或配置起来有点绕,让人感觉有些距离
版权所有 IT知识库 CopyRight © 2009-2015 IT知识库 IT610.com , All Rights Reserved. 京ICP备09083238号