可以看到getExtensionLoader
是静态的,里面逻辑也很简单就是从缓存找接口对应的 ExtensionLoader,找不到就新建一个返回。
现在有了 ExtensionLoader,咱们再来看看 getExtension 的逻辑,来看看是如何通过扩展点 name 找到对应的实现类的。
可以看到又是有个缓存操作,逻辑非常简单,先去缓存找实例,如果没有则创建实例。
要说**细节就是用到了双检锁,然后用 holder 来保证可见性和防止指令重排。**应该看到注释上的 holder 构造了吧,volatile 和双检锁的搭配,这里就不深入了。
我们来看看 createExtension,这是要创建扩展点了,代码有点长,但是我都做了相应的注释,包括绿色的注释。
逻辑还是很简单的,详细的代码没有具体展示,我先口述一下。
- 通过接口类名去三个目录找到对应的文件。
- 解析文件内容生成 class 对象,然后缓存到 cachedClasses 中。
- 然后通过 name 去 cachedClasses 中找到对应的 class 对象。
- 去缓存 EXTENSION_INSTANCES 看看是否已经实例化过了。
- 没有的话就实例化,然后调用 injectExtension 实现自动注入。
- 再通过 cachedWrapperClasses 实现包装,将最后的包装类返回。
有几点不清晰没关系,咱们接着分析,脑海中先大概知道要做什么,然后再来看看具体是怎么做的。
源码中的 loadDirectory 就是去目录找文件,然后解析,最终会调用 loadClass,这个方法很关键,我们详细分析一下,为了便于观看,删除了一些代码。
自适应咱们先略过,只要知道是在这里记录的即可。
然后上面提到的 AOP 相关的 cachedWrapperClasses 就是在这里记录的,如果判断它是包装类呢?
简单粗暴但是有效,只要有当前类作为构造器参数的类就是包装类,有点拗口,多读几遍就理解了。
现在我们再回过头来看看这段代码,Dubbo 的 AOP。
把扩展类对应的包装类都记录下来放在 cachedWrapperClasses 中,然后在实例化扩展类的时候就一层一层的把扩展类包起来,最终返回的就是包装类。
为什么说这就是 AOP 呢?因为等于把一些逻辑切进了扩展实现类中。
其实就是把扩展对象的公共逻辑移到包装类中,我们看下单元测试的例子就很清晰了。
然后再看一下单元测试的运行结果,可以看到最终返回的其实是 Ext5Wrapper1 对象,并且它还包着 wrapper2 对象。
所以 echo 方法的调用链就是: Ext5Wrapper1 ->Ext5Wrapper2->Ext5impl1
也就起到了 AOP 的效果。
接下来我们再来看看 injectExtension,是如何实现 Dubbo 的自动注入。
看了代码之后是不是有点失望,就这?
是的就是这么朴素地判断有没有 set 方法,然后根据参数找到对象,执行 set 方法注入即可。
所以说源码之下无秘密,看起来好像很高级的东西,就这。
上面代码中还有个objectFactory.getExtension()
,这个和扩展自适应有关系,还有个@Activate
也没说。
这些内容还是有点多的,也很重要,感觉上可能还有点绕,所以单独写一篇说。
最后
Dubbo 就是靠自己实现的 SPI 机制把通信协议、序列化格式、负载均衡、路由策略等各部分抽出来作为插件,实现扩展和定制。
通过微内核和SPI 机制来满足用户定制化的需求,也保证了框架本身的稳定性和可持续性。
并且 Dubbo 自身也提供了很多已有的实现,像各种路由策略等等。
所以说一个好的框架不仅自己功能要全,还得对扩展开放,这样生态才会壮大。
今天的代码还是有点多的,如果看不懂的建议下载源码,跟着源码调试几遍就清晰了。
源码这一步一定要迈过去,迈过去了之后就轻松了。
Dubbo 系列持续更新,敬请期待,有问题可以留言,我会尽量解答。