在分析dubbo源码的过程中,发现dubbo对于扩展点的加载实现的是非常巧妙的,可以达到 用时才动态实例化对象 ,灵活且节约资源。其实Dubbo 的扩展点加载是从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。它优化了JDK必须一次性实例化扩展点所有实现的缺点。
一个接口可以有多个不同的实现类,但是在一些业务场景里面,我们需要根据不同的业务类型去选择具体的能够满足我当前需求的实现类。大多数时候,我们都是在内存里面维护一个Map,这样可以很高效的实现咱们的目的。但是这样扩展性太差了,每次增加一种实现类,都得去修改原来的代码,风险太大了。于是,JDK给咱们提供了SPI技术,用来去解决这一块的短板。具体的操作方式如下:
package github.com.crazyStrongboy.inter;
package github.com.crazyStrongboy.jdk_api;
在resources资源文件夹下面建立目录META-INF/services,建立接口全路径的文件,例如:
这里 drivers
中会加载到咱们默认给的两个实现类,如果要增加一个实现,咱们只要新建一个模块,在配置文件中加上咱们的实现 github.com.crazyStrongboy.jdk_api.xxx
即可,不会入侵原来的老代码。但是这种实现的弊端也很明显,一次性加载出来了所有的扩展类,浪费资源。相关代码在 github 中。
自己去定义一个SPI的实现,主要分三步走:
我这边用的路径为 META-INF/mars_jun/
配置文件:
其实这个不算太完整,可以把每次初始化后的对象根据相应的name存储到另一个Map中,这样就不会每次调用 getExtension
都会去生成一个新的实例。但是在上面这段代码当中,咱们可以观察到,我并没有在一开始就将所有的扩展类都初始化出来,而是先保存扩展类的 Class
到Map中,直到咱们需要使用的时候再去初始化实例对象。解决了JDK中SPI会一次性实例化扩展点所有实现的这个缺陷。相关代码在 github 中。
咱们直接从下面这段代码开始:
1 private static final Protocol protocol = ExtensionLoader. 2 getExtensionLoader(Protocol.class).getAdaptiveExtension(); 复制代码
首先先普及下两个注解 @Adaptive与@SPI
:
@Adaptive @SPI
ExtensionLoader.getExtensionLoader(Protocol.class)
这一句代码只是简单的构建了一个 ExtensionLoader
扩展器加载器对象,代码不太复杂。后面的 getAdaptiveExtension
才是 重点 。顺着链路调用,会到下图所示的方法:
关注上面圈红的标记处,主要分为了两步走:
是不是感觉似曾相识~,这一块代码也就读取了dubbo指定的几个资源目录的配置,包括 "META-INF/services/" "META-INF/dubbo/" "META-INF/dubbo/internal/"
这三个目录,然后一个个解析出来,丢到指定的Map集合中,缓存起来供后期类似的操作使用。当然其中还包括一些注解的解析,是否是包装类等等一些操作,这些大家可以自行点进去了解。
在没有指定自适应的 cachedAdaptiveClass
的情况下(也就是实现类没有一个上面有@Adaptive注解),会调用 createAdaptiveExtensionClass
方法生成一个 xx$Adaptive
对象。
Dubbo的SPI机制的核心点也在这里,重点关注 xx$Adaptive 对象
。 Protocol
对应的是 Protocol$Adaptive
。咱们简单看一下它生成的代码段:
它可以根据URL中的 protocol
字段的值去动态获取相应的扩展类,例如"dubbo"对应 DubboProtocol
,"registry"对应 RegistryProtocol
,这样是不是更加的灵活~。
这一块虽然写的不多,但核心思想点也就差不多都在这一块,顺着上面的思路一步步往下读,这个东西应该不难理解~