在软件项目开发过程中,经常会碰到这样的一种情况:假定有一个公式A=B+C+1,在保存公式信息时,我们常常会用占位符(如:${})来配合公式项,即把该公式转换成A=${B}+${C}+1再保存起来。在进行公式计算时,通过占位符解析程序解析出B和C,再通过其它途径得到B和C的值,最后进行简单的数值计算得到结果。
在上面的情况里,占位符解析程序是进行公式计算的关键,接下来就来看一个类似的例子。
为了简单起见,例子只模拟如何用系统属性值去替换字符串里对应的系统属性键的占位符信息。如:系统属性:app.root,属性值:D:\\project,输入字符串为${app.root}/logs/app.log,则输出结果为:D:\project/logs/app.log。
下面显示该程序的几个场景。
场景一: | |
程序输入 | 系统属性:app.root,属性值:D:\\project ${app.root}/logs/app.log |
程序输出 | D:\project/logs/app.log |
场景二: | |
程序输入 | ${app.root:D:\project}/logs/app.log |
程序输出 | D:\project/logs/app.log |
场景三: | |
程序输入 | 系统属性:app.name,属性值:muse 系统属性:muse.root,属性值:D:\\project ${${app.name}.root}/logs/app.log |
程序输出 | D:\project/logs/app.log |
场景四: | |
程序输入 | 系统属性:root.key,属性值:${muse.root} 系统属性:muse.root,属性值:D:\\project ${root.key}/logs/app.log |
程序输出 | D:\project/logs/app.log |
该程序的接口为SystemPropertyUtils,该类负责接受用户请求。具体负责占位符处理的类由PropertyPlaceholderHelper类实现。StringUtils类为辅助的字符串处理类。
1 SystemPropertyUtils类
这是该程序的用户接口类,用于接受用户请求。该类有占位符的前缀,后缀及占位符的值分隔符三个属性,用于构造PropertyPlaceholderHelper类时使用。
public abstract class SystemPropertyUtils {
/** 系统属性占位符的前缀: "${" */
public static final String PLACEHOLDER_PREFIX = "${";
/** 系统属性占位符的后缀: "}" */
public static final String PLACEHOLDER_SUFFIX = "}";
/** 系统属性占位符的值分隔符: ":" */
public static final String VALUE_SEPARATOR = ":";
private static final PropertyPlaceholderHelper strictHelper =
new PropertyPlaceholderHelper(PLACEHOLDER_PREFIX, PLACEHOLDER_SUFFIX, VALUE_SEPARATOR, false);
private static final PropertyPlaceholderHelper nonStrictHelper =
new PropertyPlaceholderHelper(PLACEHOLDER_PREFIX, PLACEHOLDER_SUFFIX, VALUE_SEPARATOR, true);
/**
* 解析text中的${...}占位符,用相应的系统属性系统属性值。
*/
public static String resolvePlaceholders(String text) {
return resolvePlaceholders(text, false);
}
/**
* 解析text中的${...}占位符,用相应的系统属性系统属性值。如果标志位为true,则没有默认值的无法解析的占位符将保留原样不被解析。
* @param ignoreUnresolvablePlaceholders flag to determine is unresolved placeholders are ignored
*/
public static String resolvePlaceholders(String text, boolean ignoreUnresolvablePlaceholders) {
PropertyPlaceholderHelper helper = (ignoreUnresolvablePlaceholders ? nonStrictHelper : strictHelper);
return helper.replacePlaceholders(text, new SystemPropertyPlaceholderResolver(text));
}
// 这是一个私有内置类,用于从系统属性里获取占位符所对应的值。
private static class SystemPropertyPlaceholderResolver implements PropertyPlaceholderHelper.PlaceholderResolver {
private final String text;
public SystemPropertyPlaceholderResolver(String text) {
this.text = text;
}
public String resolvePlaceholder(String placeholderName) {
try {
String propVal = System.getProperty(placeholderName);
if (propVal == null) {
// 没找到系统属性时,就去查找系统环境变量。
propVal = System.getenv(placeholderName);
}
return propVal;
}
catch (Throwable ex) {
System.err.println("Could not resolve placeholder '" + placeholderName + "' in [" +
this.text + "] as system property: " + ex);
return null;
}
}
}
}
2 PropertyPlaceholderHelper类
这个类负责进行占位符处理,所有的关键处理逻辑都在这个类里。该类有占位符的前缀,后缀及占位符的值分隔符等属性,用于指明占位符的信息。
public class PropertyPlaceholderHelper {
private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<String, String>(4);
static {
wellKnownSimplePrefixes.put("}", "{");
wellKnownSimplePrefixes.put("]", "[");
wellKnownSimplePrefixes.put(")", "(");
}
private final String placeholderPrefix;
private final String placeholderSuffix;
private final String simplePrefix;
private final String valueSeparator;
private final boolean ignoreUnresolvablePlaceholders;
/**
* 构造一个使用指定的前缀和后缀PropertyPlaceholderHelper类,对于无法解析的占位符,该类不给予处理。
*/
public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix) {
this(placeholderPrefix, placeholderSuffix, null, true);
}
/**
* 构造一个使用指定的前缀和后缀PropertyPlaceholderHelper类。
*/
public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix,
String valueSeparator, boolean ignoreUnresolvablePlaceholders) {
this.placeholderPrefix = placeholderPrefix;
this.placeholderSuffix = placeholderSuffix;
String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix);
if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) {
this.simplePrefix = simplePrefixForSuffix;
}
else {
this.simplePrefix = this.placeholderPrefix;
}
this.valueSeparator = valueSeparator;
this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
}
/**
* 用placeholderResolver返回的值来代替所有的类似${name}的点位符。
*/
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
return parseStringValue(value, placeholderResolver, new HashSet<String>());
}
// 这个类是该程序的核心类。
protected String parseStringValue(
String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
StringBuilder buf = new StringBuilder(strVal);
int startIndex = strVal.indexOf(this.placeholderPrefix);
while (startIndex != -1) {
int endIndex = findPlaceholderEndIndex(buf, startIndex);
if (endIndex != -1) {
String placeholder = buf.substring(startIndex + this.placeholderPrefix.length(), endIndex);
if (!visitedPlaceholders.add(placeholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + placeholder + "' in property definitions");
}
// 递归调用,解析包含在占位符里的内嵌占位符。
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// 获取占位符的代替值。
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
// 如果没有指标占位符的代替值,则处理存在分隔符的情况。
if (propVal == null && this.valueSeparator != null) {
int separatorIndex = placeholder.indexOf(this.valueSeparator);
if (separatorIndex != -1) {
String actualPlaceholder = placeholder.substring(0, separatorIndex);
String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
if (propVal == null) {
propVal = defaultValue;
}
}
}
if (propVal != null) {
// 递归调用,处理存在于占位符代替值里的占位符。
propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
buf.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
startIndex = buf.indexOf(this.placeholderPrefix, startIndex + propVal.length());
}
else if (this.ignoreUnresolvablePlaceholders) {
startIndex = buf.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
}
else {
throw new IllegalArgumentException("Could not resolve placeholder '" + placeholder + "'");
}
visitedPlaceholders.remove(placeholder);
}
else {
startIndex = -1;
}
}
return buf.toString();
}
// 查找占位符的结束索引。如占位符为${},则查找}的索引。如果存在内嵌占位符,则跳过内嵌占位符,直接找到与前缀匹配的后缀。
private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
int index = startIndex + this.placeholderPrefix.length();
int withinNestedPlaceholder = 0;
while (index < buf.length()) {
if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) {
if (withinNestedPlaceholder > 0) {
withinNestedPlaceholder--;
index = index + this.placeholderSuffix.length();
}
else {
return index;
}
}
else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) {
withinNestedPlaceholder++;
index = index + this.simplePrefix.length();
}
else {
index++;
}
}
return -1;
}
/**
* 用于获取占位符代替值的接口。该接口由SystemPropertyUtils类的内置类去实现。
*/
public static interface PlaceholderResolver {
String resolvePlaceholder(String placeholderName);
}
}
3 StringUtils类
这是一个简单的字符串辅助处理类。处理一些常见的字符串相关的事情。
public class StringUtils {
/**
* 测试给定的字符串是否包含给定的子字符串。
*/
public static boolean substringMatch(CharSequence str, int index, CharSequence substring) {
for (int j = 0; j < substring.length(); j++) {
int i = index + j;
if (i >= str.length() || str.charAt(i) != substring.charAt(j)) {
return false;
}
}
return true;
}
}