`

Spring源码学习-PropertyPlaceholderHelper

阅读更多

今天在看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
        
    }

}


0
0
分享到:
评论

相关推荐

    最新版源码 spring-framework-5.3.10.zip

    最新版源码 spring-framework-5.3.10.zip最新版源码 spring-framework-5.3.10.zip

    spring-cglib-repack-3.2.5.jar,spring-objenesis-repack-2.6.ja及ck-2.5.1.jar

    "spring-cglib-repack-3.2.5.jar"、"spring-objenesis-repack-2.6.jar"和"spring-objenesis-repack-2.5.1.jar"这三个文件是Spring源码阅读环境中常用的依赖包,它们各自承担着不同的功能。 首先,我们来看"CGLIB"...

    Spring源码编译缺少的两个包:spring-cglib-repack-3.2.0.jar和spring-objenesis-repack-2.2.jar

    在本案例中,"Spring源码编译缺少的两个包:spring-cglib-repack-3.2.0.jar和spring-objenesis-repack-2.2.jar"揭示了这两个关键的库对于Spring源码编译是必不可少的。下面将详细讲解这两个库的作用及其在Spring中的...

    spring-security-web源码所需jar包

    1. **spring-context-3.1.2.RELEASE.jar**:提供Spring的IoC(Inversion of Control)容器和AOP(Aspect Oriented Programming)支持,这是Spring框架的基础,为Spring Security提供了配置和事件处理能力。...

    官方原版源码spring-framework-5.1.4.RELEASE.zip

    首先,源码分析从`spring-framework-5.1.4.RELEASE-dist.zip`开始,这是Spring框架的基础组件包,包含了所有核心模块的类库和配置文件。主要模块有Core Container(核心容器)、Data Access/Integration(数据访问与...

    spring-cglib-repack-3.1.jar,spring-objenesis-repack-2.1.jar

    3.1.jar"和"spring-objenesis-repack-2.1.jar"是Spring 4.0框架的重要组成部分,它们分别提供了动态子类生成和快速对象实例化的能力,对于理解Spring的内部机制,尤其是AOP和对象生命周期管理方面,具有很高的学习...

    spring源码阅读所需jar spring-cglib-repack-3.2.8.jar 和spring-objenesis-repack-2.6.jar

    阅读源码好处: 了解其整体架构与核心概念以便建立Spring的模型 从框架入口开始抽丝剥茧,理解其每一个核心概念以及作用,并将这些核心技术点融汇起来 探究每一个核心的实现细节(UML图、跑单元测试用例、DEBUG,...

    spring-data-redis-1.8.1.RELEASE-sources.jar(源码)

    spring-data-redis-1.8.1.RELEASE-sources.jar(spring-data-redis-1.8.1.RELEASE-sources.jar()

    spring源码spring-framework-4.3.2.RELEASE

    通过对Spring 4.3.2.RELEASE源码的深入研究,我们可以了解其设计理念,学习到如何优雅地管理依赖、实现面向切面编程,以及如何利用Spring构建高效、健壮的Web应用。同时,源码阅读也能帮助我们理解Spring如何与其他...

    官方原版源码spring-framework-5.1.9.RELEASE.zip

    这次我们聚焦于官方原版源码spring-framework-5.1.9.RELEASE,这个版本包含了完整的源代码,为深入理解Spring的工作原理提供了宝贵的资料。 首先,Spring Framework的核心特性在于它的依赖注入(Dependency ...

    官方原版源码spring-framework-4.3.25.RELEASE.zip

    - **spring-framework-4.3.25.RELEASE-docs.zip**:包含了官方文档,是学习和理解Spring框架的重要资源,包括API参考、开发者指南和用户手册。 - **spring-framework-4.3.25.RELEASE-schema.zip**:提供了Spring ...

    spring-framework-master

    "spring-framework-master"这一压缩包包含了Spring框架的完整源码,为我们提供了深入理解这一强大工具的绝佳机会。 Spring的核心设计理念是依赖注入(Dependency Injection,简称DI),它通过反转控制(Inversion ...

    官方源码 spring-framework-5.2.15.RELEASE.zip

    通过深入学习Spring Framework 5.2.15.RELEASE的源码,开发者不仅可以提升自身的技术能力,也能更好地利用Spring框架解决实际开发中遇到的问题,提高代码质量和开发效率。在后续的开发过程中,对源码的理解也会为...

    spring-cglib-repack-3.2.6.jar,spring-objenesis-repack-2.6.jarspring-cglib-repack

    在给定的标题和描述中,提到了两个关键的jar包:`spring-cglib-repack-3.2.6.jar` 和 `spring-objenesis-repack-2.6.jar`。这两个文件对于理解Spring框架的内部机制以及如何在IDEA中构建Spring源码至关重要。 首先...

    druid-spring-boot-starter-1.2.8.jar

    druid-spring-boot-starter-1.2.8.jar

    官方原版源码spring-framework-5.0.11.RELEASE.zip

    本篇文章将围绕"官方原版源码spring-framework-5.0.11.RELEASE.zip"展开,深入解析Spring Framework 5.0.11.RELEASE版本的关键特性,帮助读者理解其内部工作机制,提升编程技能。 首先,Spring Framework的核心设计...

    官方源码 spring-framework-5.3.4.zip

    通过对Spring Framework 5.3.4源码的深入学习,开发者不仅可以提升自己的技术能力,还能了解到更多设计原则和最佳实践。这将有助于在实际项目中编写出更高效、可维护的代码,同时也能更好地参与到Spring社区的发展中...

    官方原版源码spring-framework-5.2.3.RELEASE.zip

    `spring-5.2.3.RELEASE-docs.zip`包含的是Spring的官方文档,这对于我们理解源码至关重要。文档详细解释了每个类和方法的作用,以及如何使用它们。通过阅读文档,我们可以了解到Spring如何处理HTTP请求,如何配置...

    死磕spring源码系列-深度解析spring aop

    死磕spring源码系列-深度解析spring_aop

    spring-cglib-repack-3.2.0.jar

    下载源码,地址https://github.com/spring-projects/spring-framework/tags 下载自己想要的版本,我用的版本是 v5.0.x git clone https://github.com/spring-projects/spring-framework.git 进入目录 下载 ...

Global site tag (gtag.js) - Google Analytics