侧边栏壁纸
  • 累计撰写 251 篇文章
  • 累计创建 138 个标签
  • 累计收到 16 条评论

目 录CONTENT

文章目录

Dubbo 学习2——SPI 扩展机制

Sherlock
2019-09-10 / 0 评论 / 0 点赞 / 2574 阅读 / 15648 字 / 编辑
温馨提示:
本文最后更新于 2023-10-09,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

dubbo_architecture

1. What Is SPI ?

简单来说,就是Service Provider Interface。比如你有个接口,现在这个接口有 3 个实现类,那么在系统运行的时候对这个接口到底选择哪个实现类呢?这就需要 SPI 了,需要根据指定的配置或者是默认的配置,去找到对应的实现类加载进来,然后用这个实现类的实例对象。

SPI 机制一般用在插件扩展的场景,比如说我们开发了一个给别人使用的开源框架,如果想让别人自己写个插件,接入到我们的开源框架里面,从而扩展某个功能,这个时候 SPI 思想就派上用场了。

2. Java 中 SPI 思想的体现

Java 中 SPI 思想的经典体现大家平时基本都在用,比如说 JDBC。

Java 定义了一套 JDBC 的接口,但是 Java 并没有提供 JDBC的实现类。

一般来说,我们要根据自己使用的数据库指定具体的实现类,比如 MySQL,我们就把 mysql-jdbc-connector.jar 引入进来;Oracle,我们就把 oracle-jdbc-connector.jar 引入进来。

系统运行时,遇到使用 JDBC的接口,就会在底层使用我们引入的那个 jar 中提供的实现类。

Java SPI 有个弊端,就是一初始化就把所有实现类给加载进去。

3. Dubbo 的 SPI 思想

3.1 Dubbo 的微内核架构

Dubbp 的架构设计思想主要是两点:

  • 采用 URL 作为配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息;
  • 采用 Microkernel + Plugin **(微内核+插件)**模式,Microkernel 只负责组装 Plugin,Dubbo 自身的功能也是通过扩展点实现的,也就是 Dubbo 的所有功能点都可被用户自定义扩展所替换。

系统里抽象的各个模块,往往有很多不同的实现方案,对于好的设计来说:模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。
Dubbo 改进了 SPI 并重新命名为ExtensionLoader(扩展点机制),按照用户配置来指定加载模块,只需要约定一下路径即可。

微内核架构 (Microkernel architecture) 模式也被称为插件架构 (Plugin architecture) 模式。原本与内核集成在一起的组件会被分离出来,内核提供了特定的接口使得这些组件可以灵活的接入,这些组件在内核的管理下工作,但是这些组件可以独立的发展、更改(不会对现有系统造成改动),只要符合内核的接口即可。典型的例子比如 , Eclipse , IDEA 。

3.2 Dubbo 的微内核设计

一图胜千言:

图片来自:理解 Dubbo SPI 扩展机制

dubbo 内核对扩展是无感的,完全不知道扩展的存在,内核代码中不会出现使用具体扩展的硬编码。

术语说明 :

  • SPI : Service Provider Interface 。
  • 扩展点 : 称 Dubbo 中被@SPI注解的Interface为一个扩展点。
  • 扩展 : 被@SPI注解的interface的实现称为这个扩展点的一个扩展。

3.3 Dubbo SPI 约定

  • 1.扩展点约定 : 扩展点必须是interface类型,同时必须被@SPI注解,满足这两点才是一个扩展点。
  • 2.扩展定义约定 :在META-INF/services/$扩展点接口的全类名META-INF/dubbo/$扩展点接口的全类名META-INF/dubbo/internal/$扩展点接口的全类名,这些路径下定义的文件名称为$扩展点接口的全类名(不含.class或.java),文件中以键值对的方式配置扩展点的扩展实现。

例如文件dubbo-x.x.x.jar!\META-INF\dubbo\internal\org.apache.dubbo.rpc.Protocol中定义的扩展:

filter=org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=org.apache.dubbo.rpc.support.MockProtocol
dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
injvm=org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol
rmi=org.apache.dubbo.rpc.protocol.rmi.RmiProtocol
hessian=org.apache.dubbo.rpc.protocol.hessian.HessianProtocol
http=org.apache.dubbo.rpc.protocol.http.HttpProtocol

org.apache.dubbo.rpc.protocol.webservice.WebServiceProtocol
thrift=org.apache.dubbo.rpc.protocol.thrift.ThriftProtocol
memcached=org.apache.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=org.apache.dubbo.rpc.protocol.redis.RedisProtocol
rest=org.apache.dubbo.rpc.protocol.rest.RestProtocol
registry=org.apache.dubbo.registry.integration.RegistryProtocol
qos=org.apache.dubbo.qos.protocol.QosProtocolWrapper
  • 3.默认适应扩展 :对于被@SPI("abc")注解的interface,这个扩展点的缺省适应扩展就是 SPI 配置文件中 key 为 "abc" 的扩展。

如果存在被@Adaptive注解在类上的扩展点接口实现,那么这个类就作为扩展点的缺省适应扩展。
一个扩展点只能有一个缺省适应扩展,也就是说多个扩展中只能有一个在类上被@Adaptive注解,如果有多个 Dubbo 会抛出IllegalStateException("More than 1 adaptive class found : ")

3.4 @SPI、@Adaptive、@Activate 的作用

  • @SPI(注解在类上) : @SPI注解标识了接口是一个扩展点 ,属性 value 用来指定默认适配扩展点的名称(对应 SPI 配置文件中的 key)。
  • @Activate(注解在类型和方法上) :扩展点自动激活。@Activate注解在扩展点的实现类上,表示了一个扩展类被获取到的条件,符合条件就被获取,不符合条件就不获取。根据@Activate中的 group 、 value 属性来过滤。

具体可参考ExtensionLoader中的getActivateExtension函数。

  • @Adaptive(注解在类型和方法上):扩展点自适应。
    @Adaptive注解在类上,这个类就是缺省的适配扩展。
    @Adaptive注解在扩展点interface的方法上时,Dubbo 会动态的生成一个这个扩展点的适配扩展类。

生成代码,动态编译实例化 Class,名称为:扩展点interface的简单类名 + $Adaptive,例如 : ProxyFactory$Adpative
这么做的目的是为了在运行时去适配不同的扩展实例,在运行时通过传入的 URL 类型的参数或者内部含有获取 URL 方法的参数,从 URL 中获取到要使用的扩展类的名称,再去根据名称加载对应的扩展实例,用这个扩展实例对象调用相同的方法。
如果运行时没有适配到运行的扩展实例,那么就使用@SPI注解缺省指定的扩展。
通过这种方式就实现了运行时去适配到对应的扩展。

运行时动态生成的适配扩展类代码举例(基于 dubbo 2.7.1 版本通过CFR反编译得到) :

Protocol$Adaptive.class

package org.apache.dubbo.rpc;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.apache.dubbo.rpc.Exporter;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Protocol;
import org.apache.dubbo.rpc.RpcException;

public class Protocol$Adaptive
implements Protocol {
    @Override
    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!");
    }

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

    public Exporter export(Invoker invoker) throws RpcException {
        if (invoker == null) {
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        }
        if (invoker.getUrl() == null) {
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
        }
        URL uRL = invoker.getUrl();
		// 从 URL 中获取扩展名称 
        String extName = uRL.getProtocol() == null ? "dubbo" : uRL.getProtocol();
        if (extName == null) {
            throw new IllegalStateException(new StringBuffer().append("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (").append(uRL.toString()).append(") use keys([protocol])").toString());
        }
		// 通过扩展名称获取扩展实例对象 , 调用扩展实例对象的相同方法
        Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
        return protocol.export(invoker);
    }

    public Invoker refer(Class class_, URL uRL) throws RpcException {
        if (uRL == null) {
            throw new IllegalArgumentException("url == null");
        }
        URL uRL2 = uRL;
		// 从 URL 中获取扩展名称 , "dubbo" 是从 ExtensionLoader 对象中的 cachedDefaultName 属性获取到的
		// cachedDefaultName 是扩展点上 @SPI 注解中 value 属性指定的 
        String extName = uRL2.getProtocol() == null ? "dubbo" : uRL2.getProtocol();
        if (extName == null) {
            throw new IllegalStateException(new StringBuffer().append("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (").append(uRL2.toString()).append(") use keys([protocol])").toString());
        }
		// 通过扩展名称获取扩展实例对象 , 调用扩展实例对象的相同方法 
        Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
        return protocol.refer(class_, uRL);
    }
}

ProxyFactory$Adpative.class

package org.apache.dubbo.rpc;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.ProxyFactory;
import org.apache.dubbo.rpc.RpcException;

public class ProxyFactory$Adaptive
implements ProxyFactory {
    public Object getProxy(Invoker invoker) throws RpcException {
        if (invoker == null) {
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        }
        if (invoker.getUrl() == null) {
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
        }
        URL uRL = invoker.getUrl();
		// 从 URL 中获取扩展名称 
        String extName = uRL.getParameter("proxy", "javassist");
        if (extName == null) {
            throw new IllegalStateException(new StringBuffer().append("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (").append(uRL.toString()).append(") use keys([proxy])").toString());
        }
		// 通过扩展名称获取扩展实例对象 , 调用扩展实例对象的相同方法
        ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(extName);
        return proxyFactory.getProxy(invoker);
    }

    public Object getProxy(Invoker invoker, boolean bl) throws RpcException {
        if (invoker == null) {
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        }
        if (invoker.getUrl() == null) {
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
        }
        URL uRL = invoker.getUrl();
		// 从 URL 中获取扩展名称 
        String extName = uRL.getParameter("proxy", "javassist");
        if (extName == null) {
            throw new IllegalStateException(new StringBuffer().append("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (").append(uRL.toString()).append(") use keys([proxy])").toString());
        }
		// 通过扩展名称获取扩展实例对象 , 调用扩展实例对象的相同方法
        ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(extName);
        return proxyFactory.getProxy(invoker, bl);
    }

    public Invoker getInvoker(Object object, Class class_, URL uRL) throws RpcException {
        if (uRL == null) {
            throw new IllegalArgumentException("url == null");
        }
        URL uRL2 = uRL;
		// 从 URL 中获取扩展名称 
        String extName = uRL2.getParameter("proxy", "javassist");
        if (extName == null) {
            throw new IllegalStateException(new StringBuffer().append("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (").append(uRL2.toString()).append(") use keys([proxy])").toString());
        }
		// 通过扩展名称获取扩展实例对象 , 调用扩展实例对象的相同方法
        ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(extName);
        return proxyFactory.getInvoker(object, class_, uRL);
    }
}

其实这些代码都是模板代码,最核心的代码就只有一行,这行代码是去获取一个指定名称的扩展实例对象 :

ExtensionLoader.getExtensionLoader(Xxx.class).getExtension(extName);

3.5 扩展加载器ExtensionLoader

扩展加载器绝对是一个核心组件了 ,它控制着 dubbo 内部所有扩展点的初始化、加载扩展的过程。这个类的源码是很有必要深入学习的。

图片来自:理解 Dubbo SPI 扩展机制

ExtensionLoader中会存储两个静态属性:

  • EXTENSION_LOADERS:保存了内核开放的扩展点对应的 ExtensionLoader实例对象(说明了一种扩展点有一个对应的ExtensionLoader对象)。
  • EXTENSION_INSTANCES:保存了扩展类型(Class)和扩展类型的实例对象。

ExtensionLoader对象中的其他主要属性 :

Class<?> type;

ExtensionFactory objectFactory;

ConcurrentMap<Class<?>, String> cachedNames;

Holder<Map<String, Class<?>>> cachedClasses;

Map<String, Activate> cachedActivates;

Class<?> cachedAdaptiveClass;

ConcurrentMap<String, Holder<Object>> cachedInstances;

String cachedDefaultName;

Holder<Object> cachedAdaptiveInstance;

Throwable createAdaptiveInstanceError;

Set<Class<?>> cachedWrapperClasses;

Map<String, IllegalStateException> exceptions;
  • type : 被@SPI注解的interface,也就是扩展点。
  • objectFactory : 扩展工厂,可以从中获取到扩展类型实例对象 ,缺省为AdaptiveExtensionFactory
  • cachedNames : 保存不满足装饰模式(不存在只有一个参数,并且参数是扩展点类型实例对象的构造函数)的扩展的名称。
  • cachedClasses :保存不满足装饰模式的扩展的 Class 实例,扩展的名称作为 key , Class 实例作为 value。
  • cachedActivates : 保存不满足装饰模式,被@Activate注解的扩展的 Class 实例。
  • cachedAdaptiveClass :被@Adpative注解的扩展的 Class 实例 。
  • cachedInstances :保存扩展的名称和实例对象,扩展名称为 key,扩展实例为 value。
  • cachedDefaultName : 扩展点上@SPI注解指定的缺省适配扩展。
  • createAdaptiveInstanceError :创建适配扩展实例过程中抛出的异常。
  • cachedWrapperClasses :满足装饰模式的扩展的 Class 实例。
  • exceptions :保存在加载扩展点配置文件时,加载扩展点过程中抛出的异常,key 是当前读取的扩展点配置文件的一行,value 是抛出的异常。

4. Dubbo SPI 和 JDK SPI 区别

  • JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
  • 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。

附: dubbo 开放的扩展点

org.apache.dubbo.cache.CacheFactory
org.apache.dubbo.common.compiler.Compiler
org.apache.dubbo.common.extension.ExtensionFactory
org.apache.dubbo.common.logger.LoggerAdapter
org.apache.dubbo.common.serialize.Serialization
org.apache.dubbo.common.status.StatusChecker
org.apache.dubbo.common.store.DataStore
org.apache.dubbo.common.threadpool.ThreadPool
org.apache.dubbo.container.Container
org.apache.dubbo.container.page.PageHandler
org.apache.dubbo.monitor.MonitorFactory
org.apache.dubbo.registry.RegistryFactory
org.apache.dubbo.remoting.Codec2
org.apache.dubbo.remoting.Dispatcher
org.apache.dubbo.remoting.exchange.Exchanger
org.apache.dubbo.remoting.http.HttpBinder
org.apache.dubbo.remoting.p2p.Networker
org.apache.dubbo.remoting.telnet.TelnetHandler
org.apache.dubbo.remoting.Transporter
org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter
org.apache.dubbo.rpc.cluster.Cluster
org.apache.dubbo.rpc.cluster.ConfiguratorFactory
org.apache.dubbo.rpc.cluster.LoadBalance
org.apache.dubbo.rpc.cluster.Merger
org.apache.dubbo.rpc.cluster.RouterFactory
org.apache.dubbo.rpc.Filter
org.apache.dubbo.rpc.InvokerListener
org.apache.dubbo.rpc.Protocol
org.apache.dubbo.rpc.protocol.thrift.ClassNameGenerator
org.apache.dubbo.rpc.ProxyFactory
org.apache.dubbo.validation.Validation

参考:
理解 Dubbo SPI 扩展机制
DUBBO原理、应用与面经总结

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区