SPI全称为Service Provider Interface,是一种服务提供机制,比如在现实中我们经常会有这种场景,就是对于一个规范定义方而言(可以理解为一个或多个接口),具体的服务实现方是不可知的(可以理解为对这些接口的实现类),那么在定义这些规范的时候,就需要规范定义方能够通过一定的方式来获取到这些服务提供方具体提供的是哪些服务,而SPI就是进行这种定义的。
jdk的SPI,其主要存在两个问题,为每个接口提供的服务一般尽量只提供一个,因为jdk的SPI默认会将所有目标文件中定义的所有子类都读取到返回使用;当定义多个子类实现时,无法动态的根据配置来使用不同的配置。

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

    Dubbo SPI实现原理

    dubbo对于SPI的实现主要是在ExtensionLoader这个类中,这个类主要有三个方法

  • getExtension():主要用于获取名称为name的对应的子类的对象,这里如果子类对象如果有AOP相关的配置,这里也会对其进行封装;

  • getAdaptiveExtension():使用定义的装饰类来封装目标子类,具体使用哪个子类可以在定义的装饰类中通过一定的条件进行配置;
  • getExtensionLoader():加载当前接口的子类并且实例化一个ExtensionLoader对象。

image.png
getExtension()

  • getExtension()方法的主要作用是获取name对应的子类对象返回。
  • 其实现方式是首先读取定义文件中的子类,然后根据不同的子类对象的功能的不同,比如使用@Adaptive修饰的装饰类和用于AOP的Wrapper类,将其封装到不同的缓存中。
  • 最后根据传入的name获取其对应的子类对象,并且使用相应的Wrapper类对其进行封装。

getExtension()方法的源码
image.png
关于对于目标对象的获取,首先是从缓存里取,没取到才会进行创建。
这里需要说明的是,如果传入的name为true,那么就会返回默认的子类实例,而默认的子类实例是通过其名称进行映射的,该名称存储在目标接口的@SPI注解中。
createExtension()方法的源码
image.png
在createExtension()方法中,其主要做了三件事:

  • 加载定义文件中的各个子类,然后将目标name对应的子类返回后进行实例化。
  • 通过目标子类的set方法为其注入其所依赖的bean,这里既可以通过SPI,也可以通过Spring的BeanFactory获取所依赖的bean,injectExtension(instance)。
  • 获取定义文件中定义的wrapper对象,然后使用该wrapper对象封装目标对象,并且还会调用其set方法为wrapper对象注入其所依赖的属性。

关于wrapper对象,这里需要说明的是,其主要作用是为目标对象实现AOP。wrapper对象有两个特点:

  • a. 与目标对象实现了同一个接口;
  • b. 有一个以目标接口为参数类型的构造函数。这也就是上述createExtension()方法最后封装wrapper对象时传入的构造函数实例始终可以为instance实例的原因。

getExtensionClasses()方法的源码
image.png

  • loadExtensionClasses()主要是分别从三个目录中读取定义文件,读取该文件,并且进行缓存。

loadDirectory()方法的源码
image.png
这里主要是对每个目录进行加载,然后依次加载定义文件的内容,而对定义文件内容的处理主要是在loadResource()方法中,在对文件中每一行记录进行处理之后,其其最终是调用的loadClass()方法加载目标class的。
loadClass()方法的源码
image.png
loadClass()方法主要作用是对子类进行划分,这里主要划分成了三部分:

  • 使用@Adaptive注解标注的装饰类;
  • 包含有目标接口类型参数构造函数的wrapper类
  • 目标处理具体业务的子类。

总结而言,getExtension()方法主要是获取指定名称对应的子类。在获取过程中,首先会从缓存中获取是否已经加载过该子类,如果没加载过则通过定义文件加载,并且使用获取到的wrapper对象封装目标对象返回。
getAdaptiveExtension()

  • ExtensionLoader在加载了定义文件之后会对子类进行一个划分,使用@Adaptive进行标注的子类和使用@Adaptive标注子类方法。
  • 使用@Adaptive进行标注的子类该子类的作用主要是用于对目标类进行装饰的,从而实现一定的目的。
  • 使用@Adaptive进行标注的方法,其使用的方式主要是在目标接口的某个方法上进行标注,这个时候,dubbo就会通过javassist字节码生成工具来动态的生成目标接口的子类对象,该子类会对该接口中标注了@Adaptive注解的方法进行重写,而其余的方法则默认抛出异常,通过这种方式可以达到对特定的方法进行修饰的目的。
  • @Adaptive进行标注的方法必须要带URL参数

getAdaptiveExtension()方法源码
image.png
从缓存中获取目标类的实例,不存在则创建一个该实例。
createAdaptiveExtension()方法源码
image.png
createAdaptiveExtension()首先委托给getAdaptiveExtensionClass()方法获取一个装饰类实例,然后通过injectExtension()方法调用该实例的set方法来注入其所依赖的属性值。
对于没有使用@Adaptive标注的子类时,才会使用Javassist来为目标接口生成其子类的装饰方法。
对于使用@Adaptive标注的子类时,直接返回子类。
createAdaptiveExtensionClass()动态生成目标接口的子类字符串,然后通过javassit来编译该子类字符串,从而动态生成目标class。
getExtensionLoader()方法的源码
image.png

  • 对于ExtensionLoader的获取,其实现过程比较简单,主要是从缓存中获取,如果缓存不存在,则实例化一个并且缓存起来。
    ExtensionLoader加载流程图
    image.png
    ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension()
    ```java package org.apache.dubbo.rpc;

import org.apache.dubbo.common.extension.ExtensionLoader;

/**

  • dubbo 根据ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
  • 生成java代码内容编译生成java文件 */ public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol { public java.util.List getServers() {

    1. throw new UnsupportedOperationException("The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");

    }

    public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {

     if (arg1 == null) throw new IllegalArgumentException("url == null");
     org.apache.dubbo.common.URL url = arg1;
     String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
     if (extName == null)
         throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
     org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
     return extension.refer(arg0, arg1);
    

    }

    public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {

     if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
     if (arg0.getUrl() == null)
         throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
     org.apache.dubbo.common.URL url = arg0.getUrl();
     String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
     if (extName == null)
         throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
     org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
     return extension.export(arg0);
    

    }

    public void destroy() {

     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!");
    

    }

    public int getDefaultPort() {

     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!");
    

    } } ```