从去年年中开始,断断续续看了一些dubbo源码。刚开始方式很简单,就是在网上找一些博主的文章跟着一起学,应该说看得很痛苦,说很多东西跟看天书一样也不为过。为什么会有这种感觉?现在回想起来,最大的原因可能是当时对dubbo并没有一个系统的了解,完全不知从何入手,而且网上的很多文章往往只是讲了其中一个点,我并不知道这个东西有什么用,为什么要这么写,以及各模块之间到底是个什么关系。
现在回过头来看,如果当时能有一份类似dubbo学习进阶路线图的资料,也许这源码看起来会轻松很多。
如果现在让我从头开始,我一定不会一开始就闷头扎到源码中去。首先我会从dubbo框架设计入手,从整体设计上对它有个印象。
然后具体到源码部分,一定会从SPI入手,了解自适应扩展机制,接下来会看服务导出和服务引用,到这里,基本上hello world的demo你就知道大概是怎么跑起来的了。
当然知其然还要知其所以然,想更深入地了解其运行原理,还必须要了解其协议是怎么设计的,通信过程中的编解码、序列化都做了哪些工作。至此,简单的服务调用过程,你就有了基本的概念。
扩展到集群的场景,自然而然,你需要去了解多个生产者如何组成服务目录,消费者如何通过路由和负载均衡来选择生产者,以及相应的集群容错机制。
到这里就结束了吗?当然没有。在最新的2.7版本中,dubbo已经形成了三大中心的管理机制,即2.7之前就有的注册中心,以及在2.7中新增的元数据中心和配置中心,在dubbo进阶之路上,这三个中心你当然也需要有所了解。
除此之外,dubbo也一直在演进,在上周参加的dubbo社区开发者大会上,小马哥说将来可能会去掉元数据中心和配置中心(如果我没听错的话),以及在现在很火的云原生方面,dubbo将何去何从?如果你对dubbo感兴趣,这些都是你可以关注的一些话题。
这里给出一张自己简单整理的dubbo学习路径脑图吧。
当然,随着去年dubbo进入Apache孵化项目,官方文档上已经有了很多很有价值的资料,里面有很多dubbo commiter以及PMC成员的技术博客,甚至还有dubbo核心模块的源码解析(http://dubbo.apache.org/zh-cn/index.html),我相信这些资料可以帮助初学者更快地入门dubbo框架。
另外,一直以来,我略感困惑的一点是,作为一款优秀的开源框架,为什么市面上一直缺少一本系统介绍dubbo、适合初学者入门和进阶的图书?那么现在,这个困惑可以消除了。因为dubbo PMC成员诣极刚刚出版了这样一本书,我上面提到的一切,这本书里基本都作了详细深入的讲解,干货满满。回想起来,在上一家公司,还跟本书作者诣极大牛有过几次交流,那时候只知道他是dubbo的commiter,如今已是dubbo PMC成员了,还出了这么一本好书,果然,比我优秀的人比我还努力。
题外话
坊间一直有一种说法,说dubbo的代码写得不好,跟Spring等优秀的开源框架有不少差距。官方应该也看到这一点了,所以在dubbo重新开始维护之后,除了新特性的开发,官方也对已有功能的代码作了大量的重构和优化。这里以生成自适应扩展类代码的代码来略窥一二。
重构之前的版本是这样的,5+个for循环,15+个if分支,嵌套深度 > 5,单个函数超过300行。
private String createAdaptiveExtensionClassCode() {
...
for (Method method : methods) {
...
if (adaptiveAnnotation == null) {
...
} else {
...
if (urlTypeIndex != -1) {
...
}
else {
...
if (attribMethod == null) {
...
}
...
for (int i = value.length - 1; i >= 0; --i) {
if (i == value.length - 1) {
if (null != defaultExtName) {
if (!"protocol".equals(value[i])) {
if (hasInvocation) {
...
} else {
...
}
} else {
...
}
} else {
if (!"protocol".equals(value[i])) {
if (hasInvocation) {
...
} else {
...
}
} else {
...
}
}
} else {
if (!"protocol".equals(value[i])) {
if (hasInvocation) {
...
} else {
...
}
} else {
...
}
}
}
...
for (int i = 0; i < pts.length; i++) {
if (i != 0) {
...
}
...
}
...
}
...
for (int i = 0; i < pts.length; i++) {
if (i > 0) {
...
}
...
}
if (ets.length > 0) {
...
for (int i = 0; i < ets.length; i++) {
if (i > 0) {
...
}
...
}
}
...
}
...
}
老实说,当初看到这段代码的时候,我也是一眼蒙圈,不敢相信这真的是dubbo的代码,可以说很好地诠释了如何写出让同事不好维护的代码这一反模式。
好在今年年初,这段代码终于被重构了,重构之后的版本是这样的:
public String generate() {
// no need to generate adaptive class since there's no adaptive method found.
if (!hasAdaptiveMethod()) {
throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
}
StringBuilder code = new StringBuilder();
code.append(generatePackageInfo());
code.append(generateImports());
code.append(generateClassDeclaration());
Method[] methods = type.getMethods();
for (Method method : methods) {
code.append(generateMethod(method));
}
code.append("}");
if (logger.isDebugEnabled()) {
logger.debug(code.toString());
}
return code.toString();
}
private String generateMethod(Method method) {
String methodReturnType = method.getReturnType().getCanonicalName();
String methodName = method.getName();
String methodContent = generateMethodContent(method);
String methodArgs = generateMethodArguments(method);
String methodThrows = generateMethodThrows(method);
return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent);
}
整个过程被抽到了一个单独的类中,并按照类文件的格式被拆分成了十几个短小的方法,基本上每个方法不超过20行,这样的代码层次分明,一目了然,基本上从方法名上你就能知道他要干什么。看了下提交记录,这个重构是在今年2.11(大年初七)提交的,看来作者过年期间也没闲着😆
当然,如果不是下面这段代码的存在,可以说这是一次教科书级别的重构。不过这段代码虽然分支也较多,但好在也只有40行,读起来也不是那么难理解。有意思的是,作者显然也意识到这个问题了,标了个巨大的// TODO: refactor it在这里😆
private String generateExtNameAssignment(String[] value, boolean hasInvocation) {
// TODO: refactor it
String getNameCode = null;
for (int i = value.length - 1; i >= 0; --i) {
if (i == value.length - 1) {
if (null != defaultExtName) {
if (!"protocol".equals(value[i])) {
if (hasInvocation) {
...
} else {
...
}
} else {
...
}
} else {
if (!"protocol".equals(value[i])) {
if (hasInvocation) {
...
} else {
...
}
} else {
...
}
}
} else {
if (!"protocol".equals(value[i])) {
if (hasInvocation) {
...
}
} else {
...
}
}
}
...
}