来个例子演示一下:
左边是测试用例,右边是排序规则,下面是输出结果。
从输出结果可以看到,最终的 Filter 链取决于 list 的添加顺序。
这也就是 7757 这个 issues 说的:
list 的遍历顺序会影响到排序的顺序。
好,现在我们把排序顺序改回来,同样的测试用例再跑一次,就稳定了:
眼睛尖的朋友可能还发现了一个问题。
这个地方还有一次提交:
- 第一种判断:return o1.getSimpleName().compareTo(o2.getSimpleName())
- 第二种判断:return o1.getSimpleName().compareTo(o2.getSimpleName()) > 0 ? 1 : -1;
你说这是在干啥?
第一种判断还疏忽了这样的一种情况,包名不同但是类名相同的情况:
- com.why.a.whyFilter
- com.why.b.whyFilter
这个时候 o1.getSimpleName().compareTo(o2.getSimpleName())
返回的是 0。
返回 0 会发生啥?
直接吞掉一个 Filter 你信不信?
比如你的集合是 HashSet,或者是 TreeMap。
这就巧了,Dubbo 用的就是 TreeMap。
来个测试用例演示一下。
如果采用第一种判断,最后 TreeMap 里面只有一个 Filter 了:
如果采用第二种判断,最后 TreeMap 里面会有两个 Filter :
细节,魔鬼都在细节里面。
哎呀,真的是防不胜防啊。
好了,比较器我就说完了,但是你发现没有,我到现在都还没给你说排序过程不稳定这个 BUG 到底是啥,只是给你引申了一个其他的 BUG 出来。
莫慌,这不是我还没想好怎么给你描述嘛。
这个过程其实比较复杂,涉及到 Timsort 排序方法,就这方法就得另起一篇文章才能说清楚。
所以,我换了一个思路,主要给你看比较的过程,至于这个过程背后的原因。
就是 Timsort 在搞鬼,欢迎你自己去探索一番。
那过程是啥呢?
我在比较方法的入口处加上这样的输出语句:
测试用例是这样的:
@Test public void whyTest(){ List<Class> filters = new ArrayList<>(); filters.add(Filter4.class); filters.add(Filter3.class); filters.add(Filter2.class); filters.add(Filter1.class); filters.add(Filter5.class); Collections.sort(filters, ActivateComparator.COMPARATOR); StringBuilder builder = new StringBuilder(); for (int i = 0; i < filters.size(); i++) { builder.append(filters.get(i).getSimpleName()).append("->"); } System.out.println(builder.toString()); }
输出的日志是这样的:
这样前三次比较就能构建这样的 Filter 链:
Filter4->Filter3->Filter2->Filter1->
然后,Filter5 进来后先和 Filter1 比,发现其 Order 为 0 比 Filter1 的 -1 大,于是比较结束,得到这样的Filter 链:
Filter4->Filter3->Filter2->Filter1->Filter5->
整个过程中,Filter5 与 Filter2 完全没有发生任何比较的操作,也就更不涉及到 Filter5 里面的 before 标签了:
咦,就正确了,你就说神不神奇?
神奇吧?
为啥呢?
去看看 Timsort 的原理吧。
追根溯源
其实写到这里的我产生了一个疑问:
是谁,什么时候,引入了 after/before 机制?
因为这个机制我个人觉得出发点是挺好的,多一个配置的地方,把选择权留给用户。
但是在实际的使用中,却容易出现比较混乱的情况。
于是我看了一下提交记录:
这个注解最早是梁飞(就是 Dubbo 项目要的开创者之一)写出来的,而设计之初没有 before 和 after,但是有一个 match 和 mismatch。
然后在写出这个注解一天之后的凌晨 1 点 54 分,提交了一个方法级别的匹配:
这三个方法使用起来甚至比 before/after 更加复杂了。
于是一觉睡醒之后的 12:34 分,梁飞又删除了这三个配置:
两个月之后的 2012 年 5 月 8 日,加入了 after 和 before 配置:
然后就一直留在 Dubbo 源码里面,直到 6 年后的 2018 年 8 月 7 日,打上了不建议使用的注解:
里面说:Dubbo 源码中没有使用 after 和 before,且排序是存在问题的。
于是这两个方法,在 2.7.0 版本之后,被标注为不建议使用,宣告了该方法的死亡。
我不知道 2012 年,梁飞为什么引入了这两个方法,我也曾想从他的代码提交记录上找到点蛛丝马迹,可惜没有。
但是,有了另外的一个想法:
当年梁飞引入这两个方法后,他写的比较器,是否考虑到了这样的情况呢?
于是我马上又看了比较器的代码提交记录:
org.apache.dubbo.common.extension.support.ActivateComparator
并且把他的代码拷贝了出来,用同样的测试用例跑了一下:
很遗憾,也有一样的问题。
或许,当年就不应该引入这两个方法。
大道至简,学 Spring 的 Order,就只有一个 Order:
然后我又突然想了另外一个框架:SofaRPC。
SofaRPC 和 Dubbo 和 HSF 之间有着千丝万缕的爱恨情仇,于是我去瞅了一眼 SofaRPC 对应的地方:
com.alipay.sofa.rpc.ext.Extension
用于排序的,也就只是保留了 order。
这样比较器的代码就很简单了:
com.alipay.sofa.rpc.common.struct.OrderedComparator
另外,我顺便对比了一下梁飞最早写的比较器和现在最新的比较器的代码,功能完全一样,但是代码却差异较大:
不得不说,经过几次重构之后,最新的比较器的可读性高了很多。
我追踪了一下这个类的提交记录,也就看着这个类的一步步演化,其实算是一个比较好的代码重构的例子。
有兴趣的自己去翻一翻。
好了,就到这了,打完收工。