2.1 表达式解析

条件路由规则是一条字符串,对于 Dubbo 来说,它并不能直接理解字符串的意思,需要将其解析成内部格式才行。条件表达式的解析过程始于 ConditionRouter 的构造方法,下面一起看一下:

  1. public ConditionRouter(URL url) {
  2. this.url = url;
  3. // 获取 priority 和 force 配置
  4. this.priority = url.getParameter(Constants.PRIORITY_KEY, 0);
  5. this.force = url.getParameter(Constants.FORCE_KEY, false);
  6. try {
  7. // 获取路由规则
  8. String rule = url.getParameterAndDecoded(Constants.RULE_KEY);
  9. if (rule == null || rule.trim().length() == 0) {
  10. throw new IllegalArgumentException("Illegal route rule!");
  11. }
  12. rule = rule.replace("consumer.", "").replace("provider.", "");
  13. // 定位 => 分隔符
  14. int i = rule.indexOf("=>");
  15. // 分别获取服务消费者和提供者匹配规则
  16. String whenRule = i < 0 ? null : rule.substring(0, i).trim();
  17. String thenRule = i < 0 ? rule.trim() : rule.substring(i + 2).trim();
  18. // 解析服务消费者匹配规则
  19. Map<String, MatchPair> when =
  20. StringUtils.isBlank(whenRule) || "true".equals(whenRule)
  21. ? new HashMap<String, MatchPair>() : parseRule(whenRule);
  22. // 解析服务提供者匹配规则
  23. Map<String, MatchPair> then =
  24. StringUtils.isBlank(thenRule) || "false".equals(thenRule)
  25. ? null : parseRule(thenRule);
  26. // 将解析出的匹配规则分别赋值给 whenCondition 和 thenCondition 成员变量
  27. this.whenCondition = when;
  28. this.thenCondition = then;
  29. } catch (ParseException e) {
  30. throw new IllegalStateException(e.getMessage(), e);
  31. }
  32. }

如上,ConditionRouter 构造方法先是对路由规则做预处理,然后调用 parseRule 方法分别对服务提供者和消费者规则进行解析,最后将解析结果赋值给 whenCondition 和 thenCondition 成员变量。ConditionRouter 构造方法不是很复杂,这里就不多说了。下面我们把重点放在 parseRule 方法上,在详细介绍这个方法之前,我们先来看一个内部类。

  1. private static final class MatchPair {
  2. final Set<String> matches = new HashSet<String>();
  3. final Set<String> mismatches = new HashSet<String>();
  4. }

MatchPair 内部包含了两个 Set 类型的成员变量,分别用于存放匹配和不匹配的条件。这个类两个成员变量会在 parseRule 方法中被用到,下面来看一下。

  1. private static Map<String, MatchPair> parseRule(String rule)
  2. throws ParseException {
  3. // 定义条件映射集合
  4. Map<String, MatchPair> condition = new HashMap<String, MatchPair>();
  5. if (StringUtils.isBlank(rule)) {
  6. return condition;
  7. }
  8. MatchPair pair = null;
  9. Set<String> values = null;
  10. // 通过正则表达式匹配路由规则,ROUTE_PATTERN = ([&!=,]*)\s*([^&!=,\s]+)
  11. // 这个表达式看起来不是很好理解,第一个括号内的表达式用于匹配"&", "!", "=" 和 "," 等符号。
  12. // 第二括号内的用于匹配英文字母,数字等字符。举个例子说明一下:
  13. // host = 2.2.2.2 & host != 1.1.1.1 & method = hello
  14. // 匹配结果如下:
  15. // 括号一 括号二
  16. // 1. null host
  17. // 2. = 2.2.2.2
  18. // 3. & host
  19. // 4. != 1.1.1.1
  20. // 5. & method
  21. // 6. = hello
  22. final Matcher matcher = ROUTE_PATTERN.matcher(rule);
  23. while (matcher.find()) {
  24. // 获取括号一内的匹配结果
  25. String separator = matcher.group(1);
  26. // 获取括号二内的匹配结果
  27. String content = matcher.group(2);
  28. // 分隔符为空,表示匹配的是表达式的开始部分
  29. if (separator == null || separator.length() == 0) {
  30. // 创建 MatchPair 对象
  31. pair = new MatchPair();
  32. // 存储 <匹配项, MatchPair> 键值对,比如 <host, MatchPair>
  33. condition.put(content, pair);
  34. }
  35. // 如果分隔符为 &,表明接下来也是一个条件
  36. else if ("&".equals(separator)) {
  37. // 尝试从 condition 获取 MatchPair
  38. if (condition.get(content) == null) {
  39. // 未获取到 MatchPair,重新创建一个,并放入 condition 中
  40. pair = new MatchPair();
  41. condition.put(content, pair);
  42. } else {
  43. pair = condition.get(content);
  44. }
  45. }
  46. // 分隔符为 =
  47. else if ("=".equals(separator)) {
  48. if (pair == null)
  49. throw new ParseException("Illegal route rule ...");
  50. values = pair.matches;
  51. // 将 content 存入到 MatchPair 的 matches 集合中
  52. values.add(content);
  53. }
  54. // 分隔符为 !=
  55. else if ("!=".equals(separator)) {
  56. if (pair == null)
  57. throw new ParseException("Illegal route rule ...");
  58. values = pair.mismatches;
  59. // 将 content 存入到 MatchPair 的 mismatches 集合中
  60. values.add(content);
  61. }
  62. // 分隔符为 ,
  63. else if (",".equals(separator)) {
  64. if (values == null || values.isEmpty())
  65. throw new ParseException("Illegal route rule ...");
  66. // 将 content 存入到上一步获取到的 values 中,可能是 matches,也可能是 mismatches
  67. values.add(content);
  68. } else {
  69. throw new ParseException("Illegal route rule ...");
  70. }
  71. }
  72. return condition;
  73. }

以上就是路由规则的解析逻辑,该逻辑由正则表达式和一个 while 循环以及数个条件分支组成。下面通过一个示例对解析逻辑进行演绎。示例为 host = 2.2.2.2 & host != 1.1.1.1 & method = hello。正则解析结果如下:

  1. 括号一 括号二
  2. 1. null host
  3. 2. = 2.2.2.2
  4. 3. & host
  5. 4. != 1.1.1.1
  6. 5. & method
  7. 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 的内容如下:

  1. {
  2. "host": {
  3. "matches": ["2.2.2.2"],
  4. "mismatches": ["1.1.1.1"]
  5. },
  6. "method": {
  7. "matches": ["hello"],
  8. "mismatches": []
  9. }
  10. }

路由规则的解析过程稍微有点复杂,大家可通过 ConditionRouter 的测试类对该逻辑进行测试。并且找一个表达式,对照上面的代码走一遍,加深理解。