由浅入深理解Dubbo的SPI机制

前言

在分析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即可,不会入侵原来的老代码。但是这种实现的弊端也很明显,一次性加载出来了所有的扩展类,浪费资源。相关代码在github中。


自定义SPI实现

自己去定义一个SPI的实现,主要分三步走:

  1. 利用ClassLoader类加载器去读取资源文件夹指定名称的文件。
  2. 解析文件内容,并存放到一个Map容器中。键为name,值为具体实现类的Class。
  3. 封装一个对外提供的方法,参数为name值。
第一步:读取资源
第二步:解析配置文件内容
第三步:提供方法

我这边用的路径为META-INF/mars_jun/


配置文件:
调用方式

其实这个不算太完整,可以把每次初始化后的对象根据相应的name存储到另一个Map中,这样就不会每次调用getExtension都会去生成一个新的实例。但是在上面这段代码当中,咱们可以观察到,我并没有在一开始就将所有的扩展类都初始化出来,而是先保存扩展类的Class到Map中,直到咱们需要使用的时候再去初始化实例对象。解决了JDK中SPI会一次性实例化扩展点所有实现的这个缺陷。相关代码在github中。


Dubbo的SPI机制

咱们直接从下面这段代码开始:

1    private static final Protocol protocol = ExtensionLoader.
2            getExtensionLoader(Protocol.class).getAdaptiveExtension();
复制代码

首先先普及下两个注解@Adaptive与@SPI

  1. 一个接口的实现类至多只能有一个被@Adaptive注解,在方法上不限,注解在类上意思是标记该类为默认扩展类,标记在方法上则可支持动态的创建扩展器。
  2. @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

猜你喜欢

转载自juejin.im/post/5c74f986f265da2dac455932
今日推荐