参考官方文档:https://dubbo.apache.org/zh/docs/v2.7/dev/spi/
Dubbo 的扩展点加载机制,基于JDK标准的SPI (Service Provider Interface) 扩展点发现机制加强而来。
Dubbo改进了JDK标准的 SPI 的以下问题:

  • JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
  • 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。
  • 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。

    DUBBO SPI 改进思路

  1. 初始化DUBBO加载器ExtensionLoader时。
    1. 利用JDK SPI 加载dubbo加载策略基础类,指定dubbo扩展点配置文件位置。
      1. DubboInternalLoadingStrategy。指定目录:META-INF/dubbo/internal/
      2. DubboLoadingStrategy。指定目录:META-INF/dubbo/
      3. ServicesLoadingStrategy。指定目录:META-INF/services/
    2. 递归以上目录,初始化加载器时,加载传入的扩展点接口类型,配置的所有实现类。并缓存。
  2. 根据名称从初始化缓存中 ,获取实现类。
  3. 进行实例化、生成包装类、(根据set方法)自动注入、调用初始化方法。

    一、基本使用

    image.png

    二、SPI 特性

    @SPI:作用于扩展点的接口上,标记接口是一个扩展点。

    自动包装

    使用Wrapper 类对扩展点进行自动包装。Wrapper 类也实现了扩展点接口,但是 Wrapper 不是扩展点的真正实现。它的用途主要是包装真正的扩展点实现。即从 ExtensionLoader 中返回的实际上是 Wrapper 类的实例,Wrapper 持有了实际的扩展点实现类。
    ExtensionLoader 在加载扩展点时,如果加载到的实例有以扩展点接口为参数的构造函数,则判定为扩展点 Wrapper 类。

    1. package com.alibaba.xxx;
    2. import org.apache.dubbo.rpc.Protocol;
    3. public class XxxProtocolWrapper implements Protocol {
    4. Protocol impl;
    5. public XxxProtocolWrapper(Protocol protocol) { impl = protocol; }
    6. // 接口方法做一个操作后,再调用extension的方法
    7. public void refer() {
    8. //... 一些操作
    9. impl.refer();
    10. // ... 一些操作
    11. }
    12. // ...
    13. }

    扩展点的 Wrapper 类可以有多个。Wrapper 类可以把所有扩展点公共逻辑移至 Wrapper 中。新加的 Wrapper 在所有的扩展点上添加了逻辑,有些类似 AOP,即 Wrapper 代理了扩展点。

    自动装配

    这也是dubbo spi对传统spi机制增强的一个功能。基于setter方法实现
    加载扩展点时,扩展点实现类的成员如果为其它扩展点类型,ExtensionLoader 在会自动注入依赖的扩展点。ExtensionLoader 通过扫描扩展点实现类的所有 setter 方法来判定其成员。 ```java public interface CarMaker { Car makeCar(URL url); }

public interface WheelMaker { Wheel makeWheel(URL url); }

  1. ```java
  2. public class RaceCarMaker implements CarMaker {
  3. WheelMaker wheelMaker;
  4. public void setWheelMaker(WheelMaker wheelMaker) {
  5. this.wheelMaker = wheelMaker;
  6. }
  7. public Car makeCar(URL url) {
  8. // ...
  9. Wheel wheel = wheelMaker.makeWheel(url);
  10. // ...
  11. return new RaceCar(wheel, ...);
  12. }
  13. }

需要解决的问题:对于存在多个实现时,应该引入那个。会赋值一个代理对象(Adaptive对象)
最终需要自适应特性来解决。

自适应

生成代理类条件。1.方法被@Adaptive标记。2.方法参数中包含URL参数
ExtensionLoader 自动注入的依赖扩展点是一个 Adaptive 实例,而非具体的某个实现。因为只有在扩展点方法执行时,才能确定需要调用扩展点实现。
Dubbo 使用 URL 对象(包含了Key-Value)传递配置信息。扩展点方法调用会有URL参数(或是参数有URL成员)
依赖的扩展点也可以从URL拿到配置信息,所有的扩展点自己定好配置的Key后,配置信息利用URL从最外层传入。URL在配置传递上是一条总线。

  1. public interface CarMaker {
  2. Car makeCar(URL url);
  3. }
  4. public interface WheelMaker {
  5. Wheel makeWheel(URL url);
  6. }
  1. public class RaceCarMaker implements CarMaker {
  2. WheelMaker wheelMaker;
  3. public void setWheelMaker(WheelMaker wheelMaker) {
  4. this.wheelMaker = wheelMaker;
  5. }
  6. public Car makeCar(URL url) {
  7. // ...
  8. Wheel wheel = wheelMaker.makeWheel(url);
  9. // ...
  10. return new RaceCar(wheel, ...);
  11. }
  12. }

当执行Wheel wheel = wheelMaker.makeWheel(url);时,注入的 Adaptive 实例从url中获取事先定义好的 Key ,获取 WheelMaker 具体实现来调用真正的 makeWheel 方法。
例如:提取 wheel.type Key,即 url.get(“wheel.type”) 来决定 WheelMaker 实现。Adaptive 实例的逻辑是固定,从 URL 中提取事先定义好的 Key,动态生成真正的实现并执行它。
扩展点注入的 Adaptive 实现,是在dubbo加载扩展点时动态生成的
@Adaptive注解用在扩展接口的方法上。表示该方法是一个自适应方法。Dubbo在为扩展点生成自适应实例时,会为该方法生成对应的代码。

  1. ExtensionLoader<Protocol> extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
  2. Protocol protocol = extensionLoader.getAdaptiveExtension();
  1. package org.apache.dubbo.rpc;
  2. import org.apache.dubbo.common.extension.ExtensionLoader;
  3. public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
  4. public void destroy() {
  5. throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
  6. }
  7. public int getDefaultPort() {
  8. throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
  9. }
  10. public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
  11. if (arg0 == null)
  12. throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
  13. if (arg0.getUrl() == null)
  14. throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
  15. org.apache.dubbo.common.URL url = arg0.getUrl();
  16. String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
  17. if(extName == null)
  18. throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
  19. org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
  20. return extension.export(arg0);
  21. }
  22. public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
  23. if (arg1 == null) throw new IllegalArgumentException("url == null");
  24. org.apache.dubbo.common.URL url = arg1;
  25. String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
  26. if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
  27. org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
  28. return extension.refer(arg0, arg1);
  29. }
  30. }

Protocol接口中有四个方法,只有export和refer两个方法进行代理。因为Protocol接口在export方法和refer方法上加了@Adaptive注解,同时有URL参数。
不是加了@Adaptive注解就可以进行代理,比如:

  1. 如果是无参方法,那么则会报错
  2. 有参方法,可以有多个,并且其中某个参数类型是URL,那么则可以进行代理
  3. 有参方法,可以有多个,但是没有URL类型的参数,那么则不能进行代理
  4. 有参方法,可以有多个,没有URL类型的参数,但是参数对应的类中,存在getUrl方法(返回值类型为URL),则也可以进行代理

所以,可以发现,某个接口的Adaptive对象,在调用某个方法时,是通过该方法中的URL参数,通过调用ExtensionLoader.getExtensionLoader(XX.class).getExtension(extName);得到一个扩展点实例,然后调用该实例对应的方法。

自动激活

使用自动激活来简化配置

  1. import org.apache.dubbo.common.extension.Activate;
  2. import org.apache.dubbo.rpc.Filter;
  3. @Activate // 无条件自动激活
  4. public class XxxFilter implements Filter {
  5. // ...
  6. }
  1. import org.apache.dubbo.common.extension.Activate;
  2. import org.apache.dubbo.rpc.Filter;
  3. @Activate("xxx") // 当配置了xxx参数,并且参数为有效值时激活,比如配了cache="lru",自动激活CacheFilter。
  4. public class XxxFilter implements Filter {
  5. // ...
  6. }