在分析dubbo源码的过程当中,发现dubbo对于扩展点的加载实现的是很是巧妙的,能够达到用时才动态实例化对象,灵活且节约资源。其实Dubbo 的扩展点加载是从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制增强而来。它优化了JDK必须一次性实例化扩展点全部实现的缺点。java
一个接口能够有多个不一样的实现类,可是在一些业务场景里面,咱们须要根据不一样的业务类型去选择具体的可以知足我当前需求的实现类。大多数时候,咱们都是在内存里面维护一个Map,这样能够很高效的实现我们的目的。可是这样扩展性太差了,每次增长一种实现类,都得去修改原来的代码,风险太大了。因而,JDK给我们提供了SPI技术,用来去解决这一块的短板。具体的操做方式以下:git
package github.com.crazyStrongboy.inter;
github
package github.com.crazyStrongboy.jdk_api;
api
在resources资源文件夹下面创建目录META-INF/services,创建接口全路径的文件,例如:
缓存
这里drivers
中会加载到我们默认给的两个实现类,若是要增长一个实现,我们只要新建一个模块,在配置文件中加上我们的实现github.com.crazyStrongboy.jdk_api.xxx
便可,不会入侵原来的老代码。可是这种实现的弊端也很明显,一次性加载出来了全部的扩展类,浪费资源。相关代码在github中。app
本身去定义一个SPI的实现,主要分三步走:ide
我这边用的路径为META-INF/mars_jun/
优化
其实这个不算太完整,能够把每次初始化后的对象根据相应的name存储到另外一个Map中,这样就不会每次调用getExtension
都会去生成一个新的实例。可是在上面这段代码当中,我们能够观察到,我并无在一开始就将全部的扩展类都初始化出来,而是先保存扩展类的Class
到Map中,直到我们须要使用的时候再去初始化实例对象。解决了JDK中SPI会一次性实例化扩展点全部实现的这个缺陷。相关代码在github中。spa
我们直接从下面这段代码开始:code
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
,这样是否是更加的灵活~。
这一块虽然写的很少,但核心思想点也就差很少都在这一块,顺着上面的思路一步步往下读,这个东西应该不难理解~