前言
在分析 dubbo 源码的过程中, 发现 dubbo 对于扩展点的加载实现的是非常巧妙的, 可以达到用时才动态实例化对象, 灵活且节约资源. 其实 Dubbo 的扩展点加载是从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来. 它优化了 JDK 必须一次性实例化扩展点所有实现的缺点.
JDK 标准的 SPI
一个接口可以有多个不同的实现类, 但是在一些业务场景里面, 我们需要根据不同的业务类型去选择具体的能够满足我当前需求的实现类. 大多数时候, 我们都是在内存里面维护一个 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 即可, 不会入侵原来的老代码. 但是这种实现的弊端也很明显, 一次性加载出来了所有的扩展类, 浪费资源. 相关代码在中.
自定义 SPI 实现
自己去定义一个 SPI 的实现, 主要分三步走:
利用 ClassLoader 类加载器去读取资源文件夹指定名称的文件.
解析文件内容, 并存放到一个 Map 容器中. 键为 name, 值为具体实现类的 Class.
封装一个对外提供的方法, 参数为 name 值.
第一步: 读取资源
第二步: 解析配置文件内容
第三步: 提供方法
我这边用的路径为 META-INF/mars_jun/
配置文件:
调用方式
其实这个不算太完整, 可以把每次初始化后的对象根据相应的 name 存储到另一个 Map 中, 这样就不会每次调用 getExtension 都会去生成一个新的实例. 但是在上面这段代码当中, 咱们可以观察到, 我并没有在一开始就将所有的扩展类都初始化出来, 而是先保存扩展类的 Class 到 Map 中, 直到咱们需要使用的时候再去初始化实例对象. 解决了 JDK 中 SPI 会一次性实例化扩展点所有实现的这个缺陷. 相关代码在中.
Dubbo 的 SPI 机制
咱们直接从下面这段代码开始:
- private static final Protocol protocol = ExtensionLoader.
- 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 集合中, 缓存起来供后期类似的操作使用. 当然其中还包括一些注解的解析, 是否是包装类等等一些操作, 这些大家可以自行点进去了解.
第二步: 动态编译 Protocol$Adaptive 文件
在没有指定自适应的 cachedAdaptiveClass 的情况下 (也就是实现类没有一个上面有 @Adaptive 注解), 会调用 createAdaptiveExtensionClass 方法生成一个 xx$Adaptive 对象.
Dubbo 的 SPI 机制的核心点也在这里, 重点关注 xx$Adaptive 对象. Protocol 对应的是 Protocol$Adaptive. 咱们简单看一下它生成的代码段:
它可以根据 URL 中的 protocol 字段的值去动态获取相应的扩展类, 例如 "dubbo" 对应 DubboProtocol,"registry" 对应 RegistryProtocol, 这样是不是更加的灵活~.
这一块虽然写的不多, 但核心思想点也就差不多都在这一块, 顺着上面的思路一步步往下读, 这个东西应该不难理解~
END
来源: https://juejin.im/post/5c74f986f265da2dac455932