2.2.4 使用 get* 和 set* 访问 / 设置配置项
1. get*
get* 一共代表 21 个方法,它们用于在 Configuration 对象中获取相应的配置信息。这
些配置信息可以是 boolean(getBoolean)、int(getInt)、long(getLong)等基本类型,也
可以是其他一些 Hadoop 常用类型,如类的信息(getClassByName、getClasses、getClass)、
String 数 组(getStringCollection、getStrings)、URL(getResource) 等。 这 些 方 法 里 最
重要的是 get() 方法,它根据配置项的键获取对应的值,如果键不存在,则返回默认值
defaultValue。其他的方法都会依赖于 Configuration.get(),并在 get() 的基础上做进一步处
理。get() 方法如下:
public String get(String name, String defaultValue)
Configuration.get() 会调用 Configuration 的私有方法 substituteVars(),该方法会完成配置
的属性扩展。属性扩展是指配置项的值包含 ${key} 这种格式的变量,这些变量会被自动替
换成相应的值。也就是说,${key} 会被替换成以 key 为键的配置项的值。注意,如果 ${key}
替换后,得到的配置项值仍然包含变量,这个过程会继续进行,直到替换后的值中不再出现
变量为止。
substituteVars 的工作依赖于正则表达式:
varPat:\$\{[^\}\$ ]+\}
由于“$”、左花括号“{”、右花括号“}”都是正则表达式中的保留字,因此需要通过
“\”进行转义。正则表达式 varPat 中,“\$\{”部分用于匹配 ${key} 中的 key 前面的“${”,
最后的“\}”部分匹配属性扩展项的右花括号“}”,中间部分“[^\}\$ ]+”用于匹配属性扩展
键,它使用了两个正则表达式规则:
[^ ] 规则,通过 [^ ] 包含一系列的字符,使表达式匹配这一系列字符以外的任意一个 ❑
字符。也就是说,“[^\}\$ ]”将匹配除了“}”、“$”和空格以外的所有字符。注意,
$ 后面还包含了一个空格,这个看不见的空格,是通过空格的 Unicode 字符 \u0020 添
加到表达式中的。
+ 是一个修饰匹配次数的特殊符号,通过该符号保证了“+”前面的表达式“[^\}\$ ]” ❑
至少出现 1 次。
通过正则表达式“\$\{[^\}\$ ]+\}”,可以在输入字符串里找出需要进行属性扩展的地方,
并通过字符串替换,进行属性扩展。
前面提过,如果一次属性扩展完成以后,得到的表达式里仍然包含可扩展的变量,那
么,substituteVars() 需要再次进行属性扩展。考虑下面的情况:
属性扩展 ${key1} 的结果包含属性扩展 ${key2},而对 ${key2} 进行属性扩展后,产生
了一个包含 ${key1} 的新结果,这会导致属性扩展进入死循环,没办法停止。
针对这种可能发生的情况,substituteVars() 中使用了一个非常简单而又有效的策略,即
属性扩展只能进行一定的次数(20 次,通过 Configuration 的静态成员变量 MAX_SUBST 定
义),避免出现上面分析的属性扩展死循环。
最后一点需要注意的是,substituteVars() 中进行的属性扩展,不但可以使用保存在
Configuration 对象中的键 – 值对,而且还可以使用 Java 虚拟机的系统属性。如系统属
性 user.home 包含了当前用户的主目录,如果用户有一个配置项需要使用这个信息,可
以通过属性扩展 ${user.home},来获得对应的系统属性值。而且,Java 命令行可以通过
“-D=”的方式定义系统属性。这就提供了一个通过命令行,覆盖或者设置
Hadoop 运行时配置信息的方法。在 substituteVars() 中,属性扩展优先使用系统属性,然后
才是 Configuration 对象中保存的键 – 值对。具体代码如下:
// 正则表达式对象,包含正则表达式 \$\{[^\}\$ ]+\}
// 注意,u0020 前面只有一个”\”,转义发生在 Java 里,不在正则表达式里
private static Pattern varPat =
Pattern.compile("\\$\\{[^\\}\\$\u0020]+\\}");
// 最多做 20 次属性扩展
private static int MAX_SUBST = 20;
private String substituteVars(String expr) {
if (expr == null) {
return null;
}
Matcher match = varPat.matcher("");
String eval = expr;
// 循环,最多做 MAX_SUBST 次属性扩展
for(int s=0; s
match.reset(eval);
if (!match.find()) {
return eval; // 什么都没有找到,返回
}
String var = match.group();
var = var.substring(2, var.length()-1); // 获得属性扩展的键
String val = null;
try {
// 看看系统属性里有没有 var 对应的 val
// 这一步保证了我们首先使用系统属性做属性扩展
val = System.getProperty(var);
} catch(SecurityException se) {
LOG.warn("Unexpected SecurityException in Configuration", se);
}
if (val == null) {
// 看看 Configuration 保存的键 - 值对里有没有 var 对应的 val
val = getRaw(var);
}
if (val == null) {
// 属性扩展中的 var 没有绑定,不做扩展,返回
return eval;
}
// 替换 ${……},完成属性扩展
eval = eval.substring(0,match.start())
+val+eval.substring(match.end());
}
// 属性扩展次数过多,抛异常
throw new IllegalStateException(……);
}
2. set*
相对于 get* 来说,set* 的大多数方法都很简单,这些方法对输入进行类型转换等处理后,
最终都调用了下面的 Configuration.set() 方法:
public String set(String name, String value)
对 比 相 对 复 杂 的 Configuration.get(), 成 员 函 数 set() 只 是 简 单 地 调 用 了 成 员 变 量
properties 和 overlay 的 setProperty() 方法,保存传入的键 – 值对。