2.1 表达式解析
条件路由规则是一条字符串,对于 Dubbo 来说,它并不能直接理解字符串的意思,需要将其解析成内部格式才行。条件表达式的解析过程始于 ConditionRouter 的构造方法,下面一起看一下:
public ConditionRouter(URL url) {
this.url = url;
// 获取 priority 和 force 配置
this.priority = url.getParameter(Constants.PRIORITY_KEY, 0);
this.force = url.getParameter(Constants.FORCE_KEY, false);
try {
// 获取路由规则
String rule = url.getParameterAndDecoded(Constants.RULE_KEY);
if (rule == null || rule.trim().length() == 0) {
throw new IllegalArgumentException("Illegal route rule!");
}
rule = rule.replace("consumer.", "").replace("provider.", "");
// 定位 => 分隔符
int i = rule.indexOf("=>");
// 分别获取服务消费者和提供者匹配规则
String whenRule = i < 0 ? null : rule.substring(0, i).trim();
String thenRule = i < 0 ? rule.trim() : rule.substring(i + 2).trim();
// 解析服务消费者匹配规则
Map<String, MatchPair> when =
StringUtils.isBlank(whenRule) || "true".equals(whenRule)
? new HashMap<String, MatchPair>() : parseRule(whenRule);
// 解析服务提供者匹配规则
Map<String, MatchPair> then =
StringUtils.isBlank(thenRule) || "false".equals(thenRule)
? null : parseRule(thenRule);
// 将解析出的匹配规则分别赋值给 whenCondition 和 thenCondition 成员变量
this.whenCondition = when;
this.thenCondition = then;
} catch (ParseException e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
如上,ConditionRouter 构造方法先是对路由规则做预处理,然后调用 parseRule 方法分别对服务提供者和消费者规则进行解析,最后将解析结果赋值给 whenCondition 和 thenCondition 成员变量。ConditionRouter 构造方法不是很复杂,这里就不多说了。下面我们把重点放在 parseRule 方法上,在详细介绍这个方法之前,我们先来看一个内部类。
private static final class MatchPair {
final Set<String> matches = new HashSet<String>();
final Set<String> mismatches = new HashSet<String>();
}
MatchPair 内部包含了两个 Set 类型的成员变量,分别用于存放匹配和不匹配的条件。这个类两个成员变量会在 parseRule 方法中被用到,下面来看一下。
private static Map<String, MatchPair> parseRule(String rule)
throws ParseException {
// 定义条件映射集合
Map<String, MatchPair> condition = new HashMap<String, MatchPair>();
if (StringUtils.isBlank(rule)) {
return condition;
}
MatchPair pair = null;
Set<String> values = null;
// 通过正则表达式匹配路由规则,ROUTE_PATTERN = ([&!=,]*)\s*([^&!=,\s]+)
// 这个表达式看起来不是很好理解,第一个括号内的表达式用于匹配"&", "!", "=" 和 "," 等符号。
// 第二括号内的用于匹配英文字母,数字等字符。举个例子说明一下:
// host = 2.2.2.2 & host != 1.1.1.1 & method = hello
// 匹配结果如下:
// 括号一 括号二
// 1. null host
// 2. = 2.2.2.2
// 3. & host
// 4. != 1.1.1.1
// 5. & method
// 6. = hello
final Matcher matcher = ROUTE_PATTERN.matcher(rule);
while (matcher.find()) {
// 获取括号一内的匹配结果
String separator = matcher.group(1);
// 获取括号二内的匹配结果
String content = matcher.group(2);
// 分隔符为空,表示匹配的是表达式的开始部分
if (separator == null || separator.length() == 0) {
// 创建 MatchPair 对象
pair = new MatchPair();
// 存储 <匹配项, MatchPair> 键值对,比如 <host, MatchPair>
condition.put(content, pair);
}
// 如果分隔符为 &,表明接下来也是一个条件
else if ("&".equals(separator)) {
// 尝试从 condition 获取 MatchPair
if (condition.get(content) == null) {
// 未获取到 MatchPair,重新创建一个,并放入 condition 中
pair = new MatchPair();
condition.put(content, pair);
} else {
pair = condition.get(content);
}
}
// 分隔符为 =
else if ("=".equals(separator)) {
if (pair == null)
throw new ParseException("Illegal route rule ...");
values = pair.matches;
// 将 content 存入到 MatchPair 的 matches 集合中
values.add(content);
}
// 分隔符为 !=
else if ("!=".equals(separator)) {
if (pair == null)
throw new ParseException("Illegal route rule ...");
values = pair.mismatches;
// 将 content 存入到 MatchPair 的 mismatches 集合中
values.add(content);
}
// 分隔符为 ,
else if (",".equals(separator)) {
if (values == null || values.isEmpty())
throw new ParseException("Illegal route rule ...");
// 将 content 存入到上一步获取到的 values 中,可能是 matches,也可能是 mismatches
values.add(content);
} else {
throw new ParseException("Illegal route rule ...");
}
}
return condition;
}
以上就是路由规则的解析逻辑,该逻辑由正则表达式和一个 while 循环以及数个条件分支组成。下面通过一个示例对解析逻辑进行演绎。示例为 host = 2.2.2.2 & host != 1.1.1.1 & method = hello
。正则解析结果如下:
括号一 括号二
1. null host
2. = 2.2.2.2
3. & host
4. != 1.1.1.1
5. & method
6. = hello
现在线程进入 while 循环:
第一次循环:分隔符 separator = null,content = “host”。此时创建 MatchPair 对象,并存入到 condition 中,condition = {“host”: MatchPair@123}
第二次循环:分隔符 separator = “=”,content = “2.2.2.2”,pair = MatchPair@123。此时将 2.2.2.2 放入到 MatchPair@123 对象的 matches 集合中。
第三次循环:分隔符 separator = “&”,content = “host”。host 已存在于 condition 中,因此 pair = MatchPair@123。
第四次循环:分隔符 separator = “!=”,content = “1.1.1.1”,pair = MatchPair@123。此时将 1.1.1.1 放入到 MatchPair@123 对象的 mismatches 集合中。
第五次循环:分隔符 separator = “&”,content = “method”。condition.get(“method”) = null,因此新建一个 MatchPair 对象,并放入到 condition 中。此时 condition = {“host”: MatchPair@123, “method”: MatchPair@ 456}
第六次循环:分隔符 separator = “=”,content = “2.2.2.2”,pair = MatchPair@456。此时将 hello 放入到 MatchPair@456 对象的 matches 集合中。
循环结束,此时 condition 的内容如下:
{
"host": {
"matches": ["2.2.2.2"],
"mismatches": ["1.1.1.1"]
},
"method": {
"matches": ["hello"],
"mismatches": []
}
}
路由规则的解析过程稍微有点复杂,大家可通过 ConditionRouter 的测试类对该逻辑进行测试。并且找一个表达式,对照上面的代码走一遍,加深理解。