他山之石可以攻玉。
Spring為我們提供了一個PropertyPlaceholderConfigurer,它能夠使Bean在配置時引用外部屬性文件。
可以將BeanFactory定義中的一些屬性值放到另一個單獨的標準Java Properties文件中。
我們在部署應用時只需要在屬性文件中對一些屬性進行修改,而不用對主XML定義文件或容器所用文件進行復雜和危險的修改。
讓我們看看下面的例子片段:
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:jdbc.properties</value>
</list>
</property>
</bean>
<bean id="proxoolDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${driver}" />
<property name="url" value="${dburl}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
</bean>
jdbc.properties:
driver=oracle.jdbc.OracleDriver
dburl=jdbc:oracle:thin:@localhost:1521:root
username=myusername
password=mypassword
相信上面的配置大家都用到過,
如此配置后 xml 文件中的 "${***}"占位符會被替換成jdbc.properties中對應的屬性值。
現在我有一個需求,要求在DB中配置一些參數,如數據庫的用戶名、密碼等,我在參數中提供一個模板,
形如 jdbc:oracle:thin:@${host}:${port:1521}:${service_name}。
然后host、port、service_name從參數表中取得,然后進行替換。
于是,我想到了Spring為我們提供的PropertyPlaceholderConfigurer.java,在看了代碼之后,將字符串替換的代碼摘出來,為我的需求服務。
下面是我摘出來的字符串解析替換的輔助類:
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.springframework.util.StringUtils;


public class PlaceholderUtils
{


/** *//** Default Holder prefix: "${" */
public static final String DEF_HOLDER_PREFIX = "${";

public static final int DEF_HOLDER_PREFIX_LEN = 2;


/** *//** Default Holder suffix: "}" */
public static final String DEF_HOLDER_SUFFIX = "}";

public static final int DEF_HOLDER_SUFFIX_LEN = 1;

/** *//** Never check system properties. */
public static final int SYSTEM_PROPERTIES_MODE_NEVER = 0;


/** *//**
* Check system properties if not resolvable in the specified properties.
* This is the default.
*/
public static final int SYS_PROPS_MODE_FALLBACK = 1;


/** *//**
* Check system properties first, before trying the specified properties.
* This allows system properties to override any other property source.
*/
public static final int SYS_PROPS_MODE_OVERRIDE = 2;


/** *//**
* Parse the given String value recursively, to be able to resolve
* nested Holders (when resolved property values in turn contain
* Holders again).
*
* @param strVal
* the String value to parse
* @param props
* the Properties to resolve Holders against
* @param visitedHolders
* the Holders that have already been visited
* during the current resolution attempt (used to detect circular references
* between Holders). Only non-null if we're parsing a nested Holder.
* @throws Exception
* @throws AppException
* if invalid values are encountered
* @see #resolveHolder(String, java.util.Properties, int)
*/

public static String parse(String strVal) throws Exception
{
Set<String> visitedHolders = new HashSet<String>();
return parse(strVal, null, visitedHolders, false);
}


public static String parse(String strVal, Map<Object, Object> props) throws Exception
{
Set<String> visitedHolders = new HashSet<String>();
return parse(strVal, props, visitedHolders, false);
}


public static String parse(String strVal, boolean ignoreBadHolders) throws Exception
{
Set<String> visitedHolders = new HashSet<String>();
return parse(strVal, null, visitedHolders, ignoreBadHolders);
}

private static String parse(String strVal, Map<Object, Object> props,

Set<String> visitedHolders, boolean ignoreBadHolders) throws Exception
{

StringBuffer buf = new StringBuffer(strVal);
int startIndex = strVal.indexOf(DEF_HOLDER_PREFIX);

while (startIndex != -1)
{
int endIndex = findHolderEndIndex(buf, startIndex);

if (endIndex != -1)
{
String holder = buf.substring(startIndex + DEF_HOLDER_PREFIX_LEN, endIndex);
String defValue = null;
int defIndex = org.apache.commons.lang.StringUtils.lastIndexOf(holder, ":");

if (defIndex >= 0)
{
defValue = StringUtils.trimWhitespace(holder.substring(defIndex + 1));
holder = StringUtils.trimWhitespace(holder.substring(0, defIndex));
}


if (!visitedHolders.add(holder))
{
throw new Exception("Circular PlaceHolder reference '" + holder
+ "' in property definitions");
}
// Recursive invocation, parsing Holders contained in the Holder key.
holder = parse(holder, props, visitedHolders, ignoreBadHolders);
// Now obtain the value for the fully resolved key
String propVal = resolveHolder(holder, props, SYS_PROPS_MODE_FALLBACK, defValue);

if (propVal != null)
{
// Recursive invocation, parsing Holders contained in the
// previously resolved Holder value.
propVal = parse(propVal, props, visitedHolders, ignoreBadHolders);
buf.replace(startIndex, endIndex + DEF_HOLDER_SUFFIX_LEN, propVal);
startIndex = buf.indexOf(DEF_HOLDER_PREFIX, startIndex + propVal.length());

} else if (ignoreBadHolders)
{
// Proceed with unprocessed value.
startIndex = buf.indexOf(DEF_HOLDER_PREFIX, endIndex + DEF_HOLDER_SUFFIX_LEN);

} else
{
throw new Exception("Could not resolve Placeholder '" + holder + "'");
}
visitedHolders.remove(holder);

} else
{
startIndex = -1;
}
}

return buf.toString();
}


private static int findHolderEndIndex(CharSequence buf, int startIndex)
{
int index = startIndex + DEF_HOLDER_PREFIX_LEN;
int withinNestedHolder = 0;

while (index < buf.length())
{

if (StringUtils.substringMatch(buf, index, DEF_HOLDER_SUFFIX))
{

if (withinNestedHolder > 0)
{
withinNestedHolder--;
index = index + DEF_HOLDER_SUFFIX_LEN;

} else
{
return index;
}

} else if (StringUtils.substringMatch(buf, index, DEF_HOLDER_PREFIX))
{
withinNestedHolder++;
index = index + DEF_HOLDER_PREFIX_LEN;

} else
{
index++;
}
}
return -1;
}


/** *//**
* Resolve the given Holder using the given properties, performing
* a system properties check according to the given mode.
* <p>
* Default implementation delegates to <code>resolveHolder
* (Holder, props)</code> before/after the system properties check.
* <p>
* Subclasses can override this for custom resolution strategies, including customized points
* for the system properties check.
*
* @param holder
* the Holder to resolve
* @param props
* the merged properties of this configurer
* @param sysPropsMode
* the system properties mode,
* according to the constants in this class
* @return the resolved value, of null if none
* @see #setSystemPropertiesMode
* @see System#getProperty
* @see #resolveHolder(String, java.util.Properties)
*/
private static String resolveHolder(String holder, Map<Object, Object> props, int sysPropsMode,

String defaultValue)
{
String propVal = null;

if (sysPropsMode == SYS_PROPS_MODE_OVERRIDE)
{
propVal = resolveSystemProperty(holder);
}

if (propVal == null)
{
propVal = resolveHolder(holder, props, defaultValue);
}

if (propVal == null && sysPropsMode == SYS_PROPS_MODE_FALLBACK)
{
propVal = resolveSystemProperty(holder);
}
return propVal;
}


/** *//**
* Resolve the given Holder using the given properties.
* The default implementation simply checks for a corresponding property key.
* <p>
* Subclasses can override this for customized Holder-to-key mappings or custom resolution
* strategies, possibly just using the given properties as fallback.
* <p>
* Note that system properties will still be checked before respectively after this method is
* invoked, according to the system properties mode.
*
* @param holder
* the Holder to resolve
* @param props
* the merged properties of this configurer
* @return the resolved value, of <code>null</code> if none
* @see #setSystemPropertiesMode
*/
private static String resolveHolder(String holder, Map<Object, Object> props,

String defaultValue)
{

if (props != null)
{
Object value = props.get(holder);

if (value != null)
{
return "" + value;

} else if (defaultValue != null)
{
return defaultValue;
}
}

return defaultValue;
}


/** *//**
* Resolve the given key as JVM system property, and optionally also as
* system environment variable if no matching system property has been found.
*
* @param key
* the Holder to resolve as system property key
* @return the system property value, or <code>null</code> if not found
* @see #setSearchSystemEnvironment
* @see java.lang.System#getProperty(String)
* @see java.lang.System#getenv(String)
*/

private static String resolveSystemProperty(String key)
{

try
{
String value = System.getProperty(key);

if (value == null)
{
value = System.getenv(key);
}
return value;

} catch (Throwable ex)
{
ex.printStackTrace();
return null;
}
}
}
下面是測試類:
import java.util.Properties;


public class PlaceholderStringTest
{

public static void main(String[] args) throws Exception
{
Properties props = new Properties();
// 在.properties文件中放置key1、key2
props.put("key1", "Hello");
props.put("key2", "World");
String str = null;
// 替換key1、key2
str = PlaceholderUtils.parse("Property:${key1}=${key2}", props);
System.out.println(str);// Property:Hello=World

// 此處要替換的是 key${index:3},先去看.properties屬性中是否有index屬性,有則替換其值
// 再去看系統屬性中是否有index屬性,有則替換其值
// 由于都沒有index屬性,所以取值為 3,也就是要替換 xxx${key3:yyy}
// 由于key3在.properties文件的屬性中、系統屬性中均沒有此屬性,所以返回默認值 yyy
str = PlaceholderUtils.parse("xxx${key${index:3}:yyy}", props);
System.out.println(str); // xxxyyy

// 在.properties文件屬性中加入index=2
props.put("index", "2");

// 此處的index屬性值為2,則替換key2的屬性值,默認值yyy被忽略了
str = PlaceholderUtils.parse("xxx${key${index:3}:yyy}", props);
System.out.println(str); // xxxWorld

// 系統屬性中加入var1
System.setProperty("var1", "IamSystem");
str = PlaceholderUtils.parse("xxx${var1}");
System.out.println(str); // xxxIamSystem

System.setProperty("var2", "System2");
str = PlaceholderUtils.parse("xxx${var1}.${var2}");
System.out.println(str);// xxxIamSystem.System2

str = PlaceholderUtils.parse("xxx${var1}.${var3}", true);
System.out.println(str); // xxxIamSystem.${var3}
props.clear();
// 模板
String dburlTmp = "jdbc:oracle:thin:@${host}:${port:1521}:${service_name}";
Properties dbProps = new Properties();
dbProps.put("host", "localhost");
dbProps.put("service_name", "root");
str = PlaceholderUtils.parse(dburlTmp, dbProps);
System.out.println(str); // jdbc:oracle:thin:@localhost:1521:root
}
}
通過上面的代碼,我們便可以實現自己的placeholder了。再加上Json Schema的校驗類,給自己的參數定義Schema,使用時校驗配置參數的正確性,然后再進行Placeholder,最后將這些參數生成Json對象,供程序使用,非常方便。
本文為原創,歡迎轉載,轉載請注明出處BlogJava。