今天在看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源码spring-framework-4.3.2.RELEASE
spring源码编译之后,各个项目可能缺失jar包 spring-cglib-repack-3.2.4.jar和spring-objenesis-repack-2.5.1.jar
官方源码 spring-framework-5.3.4.zip官方源码 spring-framework-5.3.4.zip
spring源码缺失jar。spring-cglib-repack-3.2.5.jar,spring-objenesis-repack-2.6.jar
官方原版源码spring-framework-5.1.4.RELEASE.zip
spring源码导入SpringObjenesis类报错解决,缺少jar包spring-cglib-repack-3.2.0.jar和spring-objenesis-repack-2.1.jar
官方源码 spring-framework-5.2.15.RELEASE.zip官方源码 spring-framework-5.2.15.RELEASE.zip
官方原版源码spring-framework-5.1.6.RELEASE.zip
Spring源码缺失的spring-cglib-repack-3.2.6.jar和spring-objenesis-repack-2.6.jar
官方源码 spring-framework-5.3.2.zip官方源码 spring-framework-5.3.2.zip
阅读源码好处: 了解其整体架构与核心概念以便建立Spring的模型 从框架入口开始抽丝剥茧,理解其每一个核心概念以及作用,并将这些核心技术点融汇起来 探究每一个核心的实现细节(UML图、跑单元测试用例、DEBUG,...
Spring源码编译缺少的两个包:spring-cglib-repack-3.2.0.jar和spring-objenesis-repack-2.2.jar
开发工具 spring-context-4.3.6.RELEASE开发工具 spring-context-4.3.6.RELEASE开发工具 spring-context-4.3.6.RELEASE开发工具 spring-context-4.3.6.RELEASE开发工具 spring-context-4.3.6.RELEASE开发工具 spring...
spring-data-redis-1.8.1.RELEASE-sources.jar(spring-data-redis-1.8.1.RELEASE-sources.jar()
spring4.0源码编译时缺失的两个jar包,spring-cglib-repack-3.1.jar,spring-objenesis-repack-2.1.jar
官方原版源码spring-framework-4.3.22.RELEASE.zip
spring-framework-4.2.5.RELEASE,spring4.2.5版源码。
官方原版源码spring-framework-5.1.9.RELEASE.zip
官方原版源码spring-framework-4.3.25.RELEASE.zip官方原版源码spring-framework-4.3.25.RELEASE.zip
官方原版源码 spring-framework-5.1.15.RELEASE.zip