他山之石可以攻玉。
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。