dubbo 也用了 spi 思想,不过没有用 jdk 的 spi 机制,是自己实现的一套 spi 机制。
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
Protocol 接口,在系统运行的时候,,dubbo 会判断一下应该选用这个 Protocol 接口的哪个实现类来
实例化对象来使用。
它会去找一个你配置的 Protocol,将你配置的 Protocol 实现类,加载到 jvm 中来,然后实例化对象,
就用你的那个 Protocol 实现类就可以了。
上面那行代码就是 dubbo 里大量使用的,就是对很多组件,都是保留一个接口和多个实现,然后在系
统运行的时候动态根据配置去找到对应的实现类。如果你没配置,那就走默认的实现好了,没问题。

  1. @SPI("dubbo")
  2. public interface Protocol {
  3. int getDefaultPort();
  4. @Adaptive
  5. <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
  6. @Adaptive
  7. <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
  8. void destroy();
  9. default List<ProtocolServer> getServers() {
  10. return Collections.emptyList();
  11. }
  12. }

在 dubbo 自己的 jar 里,在 /META_INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol 文件中:

  1. dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
  2. http=com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
  3. hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol

所以说,这就看到了 dubbo 的 spi 机制默认是怎么玩儿的了,其实就是 Protocol 接口,
@SPI(“dubbo”) 说的是,通过 SPI 机制来提供实现类,实现类是通过 dubbo 作为默认 key 去配置文件
里找到的,配置文件名称与接口全限定名一样的,通过 dubbo 作为 key 可以找到默认的实现类就是
com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol 。
如果想要动态替换掉默认的实现类,需要使用 @Adaptive 接口,Protocol 接口中,有两个方法加了
@Adaptive 注解,就是说那俩接口会被代理实现。

啥意思呢?

比如这个 Protocol 接口搞了俩 @Adaptive 注解标注了方法,在运行的时候会针对 Protocol 生成代理
类,这个代理类的那俩方法里面会有代理代码,代理代码会在运行的时候动态根据 url 中的 protocol 来
获取那个 key,默认是 dubbo,你也可以自己指定,你如果指定了别的 key,那么就会获取别的实现类
的实例了。

Dubbo SPI 是如何实现的?

扩展点 (Extension Point) 是一个 Java 的服务接口(接口即服务)。 扩展 (Extension) 扩展点的实现类。 扩展实例 扩展点实现类的实例。

扩展自适应实例 (Extension Adaptive Instance)

如果称它为扩展代理类,可能更好理解些。扩展的自适应实例其实就是一个 Extension 的代理,它实现了扩展点接口。在调用扩展点的接口方法时,会根据实际的参数来决定要使用哪个扩展。比如一个 IRepository 的扩展点,有一个 save 方法。有两个实现 MysqlRepository 和 MongoRepository。IRepository 的自适应实例在调用接口方法的时候,会根据 save 方法中的参数,来决定要调用哪个 IRepository 的实现。如果方法参数中有 repository=mysql,那么就调用 MysqlRepository 的 save 方法。如果 repository=mongo,就调用 MongoRepository 的 save 方法。和面向对象的延迟绑定很类似。为什么 Dubbo 会引入扩展自适应实例的概念呢?

Dubbo 中的配置有两种,一种是固定的系统级别的配置,在 Dubbo 启动之后就不会再改了。还有一种是运行时的配置,可能对于每一次的 RPC,这些配置都不同。比如在 xml 文件中配置了超时时间是 10 秒钟,这个配置在 Dubbo 启动之后,就不会改变了。但针对某一次的 RPC 调用,可以设置它的超时时间是 30 秒钟,以覆盖系统级别的配置。对于 Dubbo 而言,每一次的 RPC 调用的参数都是未知的。只有在运行时,根据这些参数才能做出正确的决定。很多时候,我们的类都是一个单例的,比如 Spring 的 bean,在 Spring bean 都实例化时,如果它依赖某个扩展点,但是在 bean 实例化时,是不知道究竟该使用哪个具体的扩展实现的。这时候就需要一个代理模式了,它实现了扩展点接口,方法内部可以根据运行时参数,动态的选择合适的扩展实现。而这个代理就是自适应实例。自适应扩展实例在 Dubbo 中的使用非常广泛,Dubbo 中,每一个扩展都会有一个自适应类,如果我们没有提供,Dubbo 会使用字节码工具为我们自动生成一个。所以我们基本感觉不到自适应类的存在。后面会有例子说明自适应类是怎么工作的。

相关概念

@SPI @SPI 注解作用于扩展点的接口上,表明该接口是一个扩展点。可以被 Dubbo 的 ExtentionLoader 加载。如果没有此 ExtensionLoader 调用会异常。 @Adaptive @Adaptive 注解用在扩展接口的方法上。表示该方法是一个自适应方法。Dubbo 在为扩展点生成自适应实例时,如果方法有 @Adaptive 注解,会为该方法生成对应的代码。方法内部会根据方法的参数,来决定使用哪个扩展。

扩展别名 和 Java SPI 不同,Dubbo 中的扩展都有一个别名,key-value 形式。

可加载路径

•META-INF/dubbo/internal•META-INF/dubbo•META-INF/services

为什么不使用 Jdk 原生的 spi 实现。

在 Dubbo 的框架中并没有使用 JDK 的 Spi 来实现,而是重写了 Spi 的实现,Dubbo 中的 Spi 形式上都是从 Jar 中加载对应的拓展类,但是 Dubbo 支持更多的加载路径,也不是通过 Iterator 的形式调用而是通过名称来定位具体的 Provider,按照需要进行加载,并非 Jdk 中的一次性全部加载,效率更高,同时支持 Provider 以类似 IOC 的形式提供。

Dubbo 自己实现 Spi 的目的

1、JDK 标准的 SPI 会一次性实例化拓展点的所有实现,如果所有的实现初始化很耗时,并没加载上也没有用,就会很浪费资源。2、如果有的拓展点加载失败,则所有的拓展点无法使用,保证功能完备。3、提供了对拓展点包装的功能(Adaptive), 并且还支持 Set 的方式对其他拓展点进行注入

Dubbo 中实现

提供服务接口与服务实现,在 / META-INF/dubbo 目录下创建一个以 “接口全限定名” 命名的文件,内容为 key-value 形式,服务接口实现类的“全限定名”。全限定名:全包名. 类名

1、接口上需要添加 “org.apache.dubbo.common.extension” 包下的 @SPI 注解,在 @SPI(“spiService”)注解中可以指定默认拓展点。无 @SPI 注解无法加载,会抛出异常。

2、在 META-INF/dubbo 目录下创建全限定名文件

3、全限定名文件中的内容是 KEY-VALUE 形式,value 是实现类的全限定类名

4、调用者使用 ExtensionLoader 获取加载

Dubbo Spi 与 JDK Spi 的不同点?

加载不同,jdk spi 是一次性加载所有服务实现,即使无用的也会加载,浪费资源,且必须在 / META-INF/services 目录下;dubbo spi 是按需加载,效率更高,支持更多的加载路径。

使用方式不用,jdk spi 通过服务接口来加载所有服务实现,返回一个服务实现的集合,调用方式是一个迭代器的模式;dubbo spi

jdk的SPI缺点

jdk的spi在查找扩展实现类的过程中,需要遍历spi配置文件中定义的所有实现类,这个过程中会将这些实现类全部实例化.
如果spi配置文件中定义了多个实现类,只需要使用其中一个实现类的时候,就会生成不必要的对象

JDK SPI 在查找扩展实现类的过程中,需要遍历 SPI 配置文件中定义的所有实现类,该过程中会将这些实现类全部实例化。如果 SPI 配置文件中定义了多个实现类,而我们只需要使用其中一个实现类时,就会生成不必要的对象。例如,org.apache.dubbo.rpc.Protocol 接口有 InjvmProtocol、DubboProtocol、RmiProtocol、HttpProtocol、HessianProtocol、ThriftProtocol 等多个实现,如果使用 JDK SPI,就会加载全部实现类,导致资源的浪费。

Dubbo 改进了 JDK 标准的 SPI 的以下问题

dubbo 的spi机制不仅仅解决了jdk的spi缺点,还对spi配置文件扩展和修改

• 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。

Dubbo SPI 不仅解决了上述资源浪费的问题,还对 SPI 配置文件扩展和修改。

spi三类目录

首先,Dubbo 按照 SPI 配置文件的用途,将其分成了三类目录。

  • META-INF/services/ 目录:该目录下的 SPI 配置文件用来兼容 JDK SPI 。
  • META-INF/dubbo/ 目录:该目录用于存放用户自定义 SPI 配置文件。
  • META-INF/dubbo/internal/ 目录:该目录用于存放 Dubbo 内部使用的 SPI 配置文件。

配置文件改成key value格式

然后,Dubbo 将 SPI 配置文件改成了 KV 格式,例如:

dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol

其中 key 被称为扩展名(也就是 ExtensionName),当我们在为一个接口查找具体实现类时,可以指定扩展名来选择相应的扩展实现。例如,这里指定扩展名为 dubbo,Dubbo SPI 就知道我们要使用:org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol 这个扩展实现类,只实例化这一个扩展实现即可,无须实例化 SPI 配置文件中的其他扩展实现类。

使用 KV 格式的 SPI 配置文件的另一个好处是:让我们更容易定位到问题。假设我们使用的一个扩展实现类所在的 jar 包没有引入到项目中,那么 Dubbo SPI 在抛出异常的时候,会携带该扩展名信息,而不是简单地提示扩展实现类无法加载。这些更加准确的异常信息降低了排查问题的难度,提高了排查问题的效率。

@SPI 注解介绍

Dubbo 中某个接口被 @SPI 注解修饰时,就表示该接口是扩展接口,前文示例中的 org.apache.dubbo.rpc.Protocol 接口就是一个扩展接口:
image.png
@SPI 注解的 value 值指定了默认 的扩展名称,例如,在通过 Dubbo SPI 加载 Protocol 接口实现时,如果没有明确指定扩展名,则默认会将 @SPI 注解的 value 值作为扩展名,即加载 dubbo 这个扩展名对应的 org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol 这个扩展实现类,相关的 SPI 配置文件在 dubbo-rpc-dubbo 模块中,如下图所示:
image.png

那 ExtensionLoader 是如何处理 @SPI 注解的呢?

ExtensionLoader 位于 dubbo-common 模块中的 extension 包中,功能类似于 JDK SPI 中的 java.util.ServiceLoader。Dubbo SPI 的核心逻辑几乎都封装在 ExtensionLoader 之中(其中就包括 @SPI 注解的处理逻辑),其使用方式如下所示:

Protocol protocol = ExtensionLoader 
        .getExtensionLoader(Protocol.class).getExtension("dubbo");

这里首先来了解一下 ExtensionLoader 中三个核心的静态字段。

  • strategies(LoadingStrategy[] 类型): LoadingStrategy 接口有三个实现(通过 JDK SPI 方式加载的),如下图所示,分别对应前面介绍的三个 Dubbo SPI 配置文件所在的目录,且都继承了 Prioritized 这个优先级接口,默认优先级是
 DubboInternalLoadingStrategy > DubboLoadingStrategy > ServicesLoadingStrateg

Dubbo的spi - 图3

  • EXTENSION_LOADERS(ConcurrentMap 类型)
    :Dubbo 中一个扩展接口对应一个 ExtensionLoader 实例,该集合缓存了全部 ExtensionLoader 实例,其中的 Key 为扩展接口,Value 为加载其扩展实现的 ExtensionLoader 实例。
  • EXTENSION_INSTANCES(ConcurrentMap, Object > 类型):该集合缓存了扩展实现类与其实例对象的映射关系。在前文示例中,Key 为 Class,Value 为 DubboProtocol 对象。

下面我们再来关注一下 ExtensionLoader 的实例字段。

  • type(Class<?> 类型):当前 ExtensionLoader 实例负责加载扩展接口。
  • cachedDefaultName(String 类型):记录了 type 这个扩展接口上 @SPI 注解的 value 值,也就是默认扩展名。
  • cachedNames(ConcurrentMap, String > 类型):缓存了该 ExtensionLoader 加载的扩展实现类与扩展名之间的映射关系。
  • cachedClasses(Holder>> 类型):缓存了该 ExtensionLoader 加载的扩展名与扩展实现类之间的映射关系。cachedNames 集合的反向关系缓存。
  • cachedInstances(ConcurrentMap<String, Holder**<Object>**> 类型):缓存了该 ExtensionLoader 加载的扩展名与扩展实现对象之间的映射关系。

ExtensionLoader.getExtensionLoader() 方法会根据扩展接口从 EXTENSION_LOADERS 缓存中查找相应的 ExtensionLoader 实例,核心实现如下:

    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        if (type == null) {
            throw new IllegalArgumentException("Extension type == null");
        } else if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
        } else if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
        } else {
            ExtensionLoader<T> loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);
            if (loader == null) {
                EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
                loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);
            }

            return loader;
        }
    }

得到接口对应的 ExtensionLoader 对象之后会调用其 getExtension() 方法,根据传入的扩展名称从 cachedInstances 缓存中查找扩展实现的实例,最终将其实例化后返回:

public T getExtension(String name) { 
  Holder<Object> holder = getOrCreateHolder(name); 
  Object instance = holder.get(); 
  if (instance == null) { 
    synchronized (holder) { 
      instance = holder.get(); 
      if (instance == null) { 
        instance = createExtension(name); 
        holder.set(instance); 
      } 
    } 
  } 
  return (T) instance; 
}

在 createExtension() 方法中完成了 SPI 配置文件的查找以及相应扩展实现类的实例化,同时还实现了自动装配以及自动 Wrapper 包装等功能。其核心流程是这样的:

  1. 获取 cachedClasses 缓存,根据扩展名从 cachedClasses 缓存中获取扩展实现类。如果 cachedClasses 未初始化,则会扫描前面介绍的三个 SPI 目录获取查找相应的 SPI 配置文件,然后加载其中的扩展实现类,最后将扩展名和扩展实现类的映射关系记录到 cachedClasses 缓存中。这部分逻辑在 loadExtensionClasses() 和 loadDirectory() 方法中。
  2. 根据扩展实现类从 EXTENSION_INSTANCES 缓存中查找相应的实例。如果查找失败,会通过反射创建扩展实现对象。
  3. 自动装配扩展实现对象中的属性(即调用其 setter)。这里涉及 ExtensionFactory 以及自动装配的相关内容,本课时后面会进行详细介绍。
  4. 自动包装扩展实现对象。这里涉及 Wrapper 类以及自动包装特性的相关内容,本课时后面会进行详细介绍。
  5. 如果扩展实现类实现了 Lifecycle 接口,在 initExtension() 方法中会调用 initialize() 方法进行初始化。
private T createExtension(String name) { 
  Class<?> clazz = getExtensionClasses().get(name); 
  if (clazz == null) { 
    throw findException(name); 
  } 
  try { 
    T instance = (T) EXTENSION_INSTANCES.get(clazz); 
    if (instance == null) { 
      EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); 
      instance = (T) EXTENSION_INSTANCES.get(clazz); 
    } 
    injectExtension(instance); 
    Set<Class<?>> wrapperClasses = cachedWrapperClasses; 
    if (CollectionUtils.isNotEmpty(wrapperClasses)) { 
      for (Class<?> wrapperClass : wrapperClasses) { 
        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); 
      } 
    } 
    initExtension(instance); 
    return instance; 
  } catch (Throwable t) { 
    throw new IllegalStateException("Extension instance (name: " + name + ", class: " + 
                                    type + ") couldn't be instantiated: " + t.getMessage(), t); 
  } 
}

@Adaptive 注解与适配器

@Adaptive 注解用来实现 Dubbo 的适配器功能,那什么是适配器呢?这里我们通过一个示例进行说明。Dubbo 中的 ExtensionFactory 接口有三个实现类,如下图所示,ExtensionFactory 接口上有 @SPI 注解,AdaptiveExtensionFactory 实现类上有 @Adaptive 注解。

Dubbo的spi - 图4

AdaptiveExtensionFactory 不实现任何具体的功能,而是用来适配 ExtensionFactory 的 SpiExtensionFactory 和 SpringExtensionFactory 这两种实现。AdaptiveExtensionFactory 会根据运行时的一些状态来选择具体调用 ExtensionFactory 的哪个实现。

@Adaptive 注解还可以加到接口方法之上,Dubbo 会动态生成适配器类。例如,Transporter 接口有两个被 @Adaptive 注解修饰的方法:

@SPI("netty") 
public interface Transporter { 

  @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY}) 

  RemotingServer bind(URL url, ChannelHandler handler) throws RemotingException; 

  @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY}) 

  Client connect(URL url, ChannelHandler handler) throws RemotingException; 

}

Dubbo 会生成一个 Transporter$Adaptive 适配器类,该类继承了 Transporter 接口:

public class Transporter$Adaptive implements Transporter { 

  public org.apache.dubbo.remoting.Client connect(URL arg0, ChannelHandler arg1) throws RemotingException { 
    if (arg0 == null) throw new IllegalArgumentException("url == null"); 
    URL url = arg0; 
    String extName = url.getParameter("client",
                                      url.getParameter("transporter", "netty")); 
    if (extName == null) 
      throw new IllegalStateException("..."); 
    Transporter extension = (Transporter) ExtensionLoader 
      .getExtensionLoader(Transporter.class) 
      .getExtension(extName); 
    return extension.connect(arg0, arg1); 
  } 
  ... 
  }

生成 Transporter$Adaptive 这个类的逻辑位于 ExtensionLoader.createAdaptiveExtensionClass() 方法,若感兴趣你可以看一下相关代码,其中涉及的 javassist 等方面的知识,在后面的课时中我们会进行介绍。

明确了 @Adaptive 注解的作用之后,我们回到 ExtensionLoader.createExtension() 方法,其中在扫描 SPI 配置文件的时候,会调用 loadClass() 方法加载 SPI 配置文件中指定的类,如下图所示:

Dubbo的spi - 图5

loadClass() 方法中会识别加载扩展实现类上的 @Adaptive 注解,将该扩展实现的类型缓存到 cachedAdaptiveClass 这个实例字段上(volatile 修饰):

private void loadClass(){ 
  if (clazz.isAnnotationPresent(Adaptive.class)) { 
    // 缓存到cachedAdaptiveClass字段 
    cacheAdaptiveClass(clazz, overridden);
  } else ... 
  }

我们可以通过 ExtensionLoader.getAdaptiveExtension() 方法获取适配器实例,并将该实例缓存到 cachedAdaptiveInstance 字段(Holder 类型)中,核心流程如下:

  • 首先,检查 cachedAdaptiveInstance 字段中是否已缓存了适配器实例,如果已缓存,则直接返回该实例即可。
  • 然后,调用 getExtensionClasses() 方法,其中就会触发前文介绍的 loadClass() 方法,完成 cachedAdaptiveClass 字段的填充。
  • 如果存在 @Adaptive 注解修饰的扩展实现类,该类就是适配器类,通过 newInstance() 将其实例化即可。如果不存在 @Adaptive 注解修饰的扩展实现类,就需要通过 createAdaptiveExtensionClass() 方法扫描扩展接口中方法上的 @Adaptive 注解,动态生成适配器类,然后实例化。
  • 接下来,调用 injectExtension() 方法进行自动装配,就能得到一个完整的适配器实例。
  • 最后,将适配器实例缓存到 cachedAdaptiveInstance 字段,然后返回适配器实例。

getAdaptiveExtension() 方法的流程涉及多个方法,这里不再粘贴代码,感兴趣的同学可以参考上述流程分析相应源码。

此外,我们还可以通过 API 方式(addExtension() 方法)设置 cachedAdaptiveClass 这个字段,指定适配器类型(这个方法你知道即可)。

总之,适配器什么实际工作都不用做,就是根据参数和状态选择其他实现来完成工作。 。

自动包装特性

Dubbo 中的一个扩展接口可能有多个扩展实现类,这些扩展实现类可能会包含一些相同的逻辑,如果在每个实现类中都写一遍,那么这些重复代码就会变得很难维护。Dubbo 提供的自动包装特性,就可以解决这个问题。 Dubbo 将多个扩展实现类的公共逻辑,抽象到 Wrapper 类中,Wrapper 类与普通的扩展实现类一样,也实现了扩展接口,在获取真正的扩展实现对象时,在其外面包装一层 Wrapper 对象,你可以理解成一层装饰器。

了解了 Wrapper 类的基本功能,我们回到 ExtensionLoader.loadClass() 方法中,可以看到:

private void loadClass(){ 


  ... 


  } else if (isWrapperClass(clazz)) { 


  cacheWrapperClass(clazz); 


} else ... 


}
  1. 在 isWrapperClass() 方法中,会判断该扩展实现类是否包含拷贝构造函数(即构造函数只有一个参数且为扩展接口类型),如果包含,则为 Wrapper 类,这就是判断 Wrapper 类的标准。
  2. 将 Wrapper 类记录到 cachedWrapperClasses(Set> 类型)这个实例字段中进行缓存。

前面在介绍 createExtension() 方法时的 4 处,有下面这段代码,其中会遍历全部 Wrapper 类并一层层包装到真正的扩展实例对象外层:

Set<Class<?>> wrapperClasses = cachedWrapperClasses;


if (CollectionUtils.isNotEmpty(wrapperClasses)) { 


  for (Class<?> wrapperClass : wrapperClasses) { 


    instance = injectExtension((T) wrapperClass 


                               .getConstructor(type).newInstance(instance)); 


  } 


}

自动装配特性

在 createExtension() 方法中我们看到,Dubbo SPI 在拿到扩展实现类的对象(以及 Wrapper 类的对象)之后,还会调用 injectExtension() 方法扫描其全部 setter 方法,并根据 setter 方法的名称以及参数的类型,加载相应的扩展实现,然后调用相应的 setter 方法填充属性,这就实现了 Dubbo SPI 的自动装配特性。简单来说,自动装配属性就是在加载一个扩展点的时候,将其依赖的扩展点一并加载,并进行装配。

下面简单看一下 injectExtension() 方法的具体实现:

private T injectExtension(T instance) { 


  if (objectFactory == null) { 


    return instance; 


  } 


  for (Method method : instance.getClass().getMethods()) { 


    ... 


      if (method.getAnnotation(DisableInject.class) != null) { 


        continue; // 如果方法上明确标注了@DisableInject注解,忽略该方法 


      } 


    Class<?> pt = method.getParameterTypes()[0]; 


    ... 


      String property = getSetterProperty(method); 


    Object object = objectFactory.getExtension(pt, property); 


    if (object != null) { 


      method.invoke(instance, object); 


    } 


  } 


  return instance; 


}

injectExtension() 方法实现的自动装配依赖了 ExtensionFactory(即 objectFactory 字段),前面我们提到过 ExtensionFactory 有 SpringExtensionFactory 和 SpiExtensionFactory 两个真正的实现(还有一个实现是 AdaptiveExtensionFactory 是适配器)。下面我们分别介绍下这两个真正的实现。

第一个,SpiExtensionFactory。 根据扩展接口获取相应的适配器,没有到属性名称:

@Override 


public <T> T getExtension(Class<T> type, String name) { 


  if (type.isInterface() && type.isAnnotationPresent(SPI.class)) { 


    // 查找type对应的ExtensionLoader实例 


    ExtensionLoader<T> loader = ExtensionLoader 


      .getExtensionLoader(type); 


    if (!loader.getSupportedExtensions().isEmpty()) { 


      return loader.getAdaptiveExtension(); 


    } 


  } 


  return null; 


}

第二个,SpringExtensionFactory。 将属性名称作为 Spring Bean 的名称,从 Spring 容器中获取 Bean:

public <T> T getExtension(Class<T> type, String name) { 


  ... 


    for (ApplicationContext context : CONTEXTS) { 


      T bean = BeanFactoryUtils.getOptionalBean(context,name,type); 


      if (bean != null) { 


        return bean; 


      } 


    } 


  return null; 


}

@Activate 注解与自动激活特性

这里以 Dubbo 中的 Filter 为例说明自动激活特性的含义,org.apache.dubbo.rpc.Filter 接口有非常多的扩展实现类,在一个场景中可能需要某几个 Filter 扩展实现类协同工作,而另一个场景中可能需要另外几个实现类一起工作。这样,就需要一套配置来指定当前场景中哪些 Filter 实现是可用的,这就是 @Activate 注解要做的事情。

@Activate 注解标注在扩展实现类上,有 group、value 以及 order 三个属性。

  • group 属性:修饰的实现类是在 Provider 端被激活还是在 Consumer 端被激活。
  • value 属性:修饰的实现类只在 URL 参数中出现指定的 key 时才会被激活。
  • order 属性:用来确定扩展实现类的排序。

我们先来看 loadClass() 方法对 @Activate 的扫描,其中会将包含 @Activate 注解的实现类缓存到 cachedActivates 这个实例字段(Map 类型,Key 为扩展名,Value 为 @Activate 注解):

private void loadClass(){ 


  if (clazz.isAnnotationPresent(Adaptive.class)) { 


    // 处理@Adaptive注解 


    cacheAdaptiveClass(clazz, overridden); 


  } else if (isWrapperClass(clazz)) { 


    cacheWrapperClass(clazz); 


  } else { 


    clazz.getConstructor(); 


    ...


      String[] names = NAME_SEPARATOR.split(name); 


    if (ArrayUtils.isNotEmpty(names)) { 


      cacheActivateClass(clazz, names[0]); 


      for (String n : names) { 


        cacheName(clazz, n);


        saveInExtensionClass(extensionClasses, clazz, n, 


                             overridden); 


      } 


    } 


  } 


}

使用 cachedActivates 这个集合的地方是 getActivateExtension() 方法。首先来关注 getActivateExtension() 方法的参数:url 中包含了配置信息,values 是配置中指定的扩展名,group 为 Provider 或 Consumer。下面是 getActivateExtension() 方法的核心逻辑:

  1. 首先,获取默认激活的扩展集合。默认激活的扩展实现类有几个条件:①在 cachedActivates 集合中存在;②@Activate 注解指定的 group 属性与当前 group 匹配;③扩展名没有出现在 values 中(即未在配置中明确指定,也未在配置中明确指定删除);④URL 中出现了 @Activate 注解中指定的 Key。
  2. 然后,按照 @Activate 注解中的 order 属性对默认激活的扩展集合进行排序。
  3. 最后,按序添加自定义扩展实现类的对象。
public List<T> getActivateExtension(URL url, String[] values, 


                                    String group) { 


  List<T> activateExtensions = new ArrayList<>(); 


  List<String> names = values == null ?


    new ArrayList<>(0) : asList(values); 


  if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {


    getExtensionClasses(); 


    for (Map.Entry<String, Object> entry :


         cachedActivates.entrySet()) { 


      String name = entry.getKey(); 


      Object activate = entry.getValue(); 


      String[] activateGroup, activateValue; 


      if (activate instanceof Activate) { 


        activateGroup = ((Activate) activate).group(); 


        activateValue = ((Activate) activate).value(); 


      } else { 


        continue; 


      } 


      if (isMatchGroup(group, activateGroup) 


          && !names.contains(name)


          && !names.contains(REMOVE_VALUE_PREFIX + name)


          && isActive(activateValue, url)) { 


        activateExtensions.add(getExtension(name)); 


      } 


    } 


    activateExtensions.sort(ActivateComparator.COMPARATOR); 


  } 


  List<T> loadedExtensions = new ArrayList<>(); 


  for (int i = 0; i < names.size(); i++) { 


    String name = names.get(i); 


    if (!name.startsWith(REMOVE_VALUE_PREFIX) 


        && !names.contains(REMOVE_VALUE_PREFIX + name)) { 


      if (DEFAULT_KEY.equals(name)) { 


        if (!loadedExtensions.isEmpty()) { 


          activateExtensions.addAll(0, loadedExtensions); 


          loadedExtensions.clear(); 


        } 


      } else { 


        loadedExtensions.add(getExtension(name)); 


      } 


    } 


  } 


  if (!loadedExtensions.isEmpty()) { 


    activateExtensions.addAll(loadedExtensions); 


  } 


  return activateExtensions; 


}

最后举个简单的例子说明上述处理流程,假设 cachedActivates 集合缓存的扩展实现如下表所示:

Dubbo的spi - 图6

在 Provider 端调用 getActivateExtension() 方法时传入的 values 配置为 “demoFilter3、-demoFilter2、default、demoFilter1”,那么根据上面的逻辑:

  1. 得到默认激活的扩展实实现集合中有 [demoFilter4, demoFilter6];
  2. 排序后为 [demoFilter6, demoFilter4];
  3. 按序添加自定义扩展实例之后得到 [demoFilter3, demoFilter6, demoFilter4, demoFilter1]。

Dubbo 中的实现

LoadBanlance 扩展点

package org.apache.dubbo.rpc.cluster;

import java.util.List;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcException;

@SPI("random")
public interface LoadBalance {
    @Adaptive({"loadbalance"})
    <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}

LoadBalance 接口只有一个 select 方法。select 方法从多个 invoker 中选择其中一个。上面代码中和 Dubbo SPI 相关的元素有:

•@SPI(“random”) @SPI 作用于 LoadBalance 接口,表示接口 LoadBalance 是一个扩展点。如果没有 @SPI 注解,试图去加载扩展时,会抛出异常。@SPI 注解有一个参数,该参数表示该扩展点的默认实现的别名。如果没有显示的指定扩展,就使用默认实现。默认实现是 “random”,是一个随机负载均衡的实现。random 的定义在配置文件 META-INF/dubbo/internal/com.alibaba.dubbo.rpc.cluster.LoadBalance中:

random=org.apache.dubbo.rpc.cluster.loadbalance.RandomLoadBalance
roundrobin=org.apache.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance
leastactive=org.apache.dubbo.rpc.cluster.loadbalance.LeastActiveLoadBalance
consistenthash=org.apache.dubbo.rpc.cluster.loadbalance.ConsistentHashLoadBalance
shortestresponse=org.apache.dubbo.rpc.cluster.loadbalance.ShortestResponseLoadBalance

@Adaptive("loadbalance")@Adaptive 注解修饰 select 方法,表明方法 select 方法是一个可自适应的方法。Dubbo 会自动生成该方法对应的代码。当调用 select 方法时,会根据具体的方法参数来决定调用哪个扩展实现的 select 方法。**@Adaptive 注解的参数 loadbalance 表示方法参数中的 loadbalance 的值作为实际要调用的扩展实例。但奇怪的是,我们发现 select 的方法中并没有 loadbalance 参数,那怎么获取 loadbalance 的值呢?select 方法中还有一个 URL 类型的参数,Dubbo 就是从 URL 中获取 loadbalance 的值的。这里涉及到 Dubbo 的 URL 总线模式,简单说,URL 中包含了 RPC 调用中的所有参数。URL 类中有一个 Map
字段,parameters 中就包含了 loadbalance。

LoadBalance lb = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(loadbalanceName);LoadBalance lb = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(loadbalanceName);

使用ExtensionLoader.getExtensionLoader(LoadBalance.class)方法获取一个ExtensionLoader的实例,然后调用getExtension,传入一个扩展的别名来获取对应的扩展实例。

总结 dubbo SPI

• 对 Dubbo 进行扩展,不需要改动 Dubbo 的源码 • 自定义的 Dubbo 的扩展点实现,是一个普通的 Java 类,Dubbo 没有引入任何 Dubbo 特有的元素,对代码侵入性几乎为零。• 将扩展注册到 Dubbo 中,只需要在 ClassPath 中添加配置文件。使用简单。而且不会对现有代码造成影响。符合开闭原则。•Dubbo 的扩展机制支持 IoC,AoP 等高级功能 •Dubbo 的扩展机制能很好的支持第三方 IOC 容器,默认支持 Spring Bean,可自己扩展来支持其他容器,比如 Google 的 Guice。• 切换扩展点的实现,只需要在配置文件中修改具体的实现,不需要改代码。使用方便。

其它文档地址

[1] 协议扩展: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/protocol/
[2] 调用拦截扩展: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/filter/
[3] 引用监听扩展: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/invoker-listener/
[4] 暴露监听扩展: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/exporter-listener/
[5] 集群扩展: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/cluster/
[6] 路由扩展: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/router/
[7] 负载均衡扩展: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/load-balance/
[8] 合并结果扩展: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/merger/
[9] 注册中心扩展: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/registry/
[10] 监控中心扩展: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/monitor/
[11] 扩展点加载扩展: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/extension-factory/
[12] 动态代理扩展: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/proxy-factory/
[13] 编译器扩展: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/compiler/
[14] Dubbo 配置中心扩展: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/config-center/
[15] 消息派发扩展: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/dispatcher/
[16] 程池扩展: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/threadpool/
[17] 序列化扩展: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/serialize/
[18] 网络传输扩展: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/remoting/
[19] 信息交换扩展: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/exchanger/
[20] 组网扩展: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/networker/
[21] Telnet 命令扩展: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/telnet-handler/
[22] 状态检查扩展: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/status-checker/
[23] 容器扩展: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/container/
[24] 缓存扩展: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/cache/
[25] 验证扩展: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/validation/
[26] 日志适配扩展: https://dubbo.apache.org/zh/docs/v2.7/dev/impls/logger-adapter/