一文让你读懂 Dubbo 中的 SPI 扩展机制

 



Java SPI (Service Provider Interface)


应用程序是内聚服务的聚合。虽然应用程序在应用程序编程接口(api)和类方面提供了更广泛的功能集,但服务提供了对某些特定应用程序功能或特性的访问。服务定义了功能的接口和检索实现的方法。

服务是一组众所周知的接口和(通常是抽象的)类。服务提供者是服务的特定实现。提供者中的类通常实现接口并继承服务本身中定义的类。服务提供者可以以扩展的形式安装在Java平台的实现中,也就是说,jar文件可以放置在任何常用的扩展目录中。提供程序也可以通过将它们添加到应用程序的类路径或其他特定于平台的方法来提供。

服务提供者实现SPI幷包含一个或多个实现或扩展服务类型(子类)的具体类。一个SPI规范可以有多个提供者。为了促进松散耦合和信息隐藏,提供者类通常不是整个提供者本身,而是一个代理,它包含足够的功能来决定提供者是否能够满足特定的请求。

可以通过简单地添加一个新的Java Archive (JAR)文件来安装服务提供者,该文件将提供者类保存到应用程序的类路径中,或者将JAR放置到任何常用的扩展目录中(jre/lib/ext)。

通过在资源目录META-INF/services中放置一个提供程序配置文件来识别服务提供者。文件的名称是服务类型的完全限定二进制名称。该文件包含具体提供程序类的完全限定二进制名称列表,每行一个。






插件式架构(Plug-in architecture)



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






Dubbo 的插件式设计


根据我个人对 Dubbo 微内核设计的理解,以及阅读源码后总结。视觉总是最直观的,可以让大脑最快速度的有一个最直观的认识,一开始就一头深入到源码的细节中只会让人迷糊。不理解 Dubbo 的微内核设计架构的话,学习起来会走不少弯路。




dubbo 内核对扩展是无感的 , 完全不知道扩展的存在 , 内核代码中不会出现使用具体扩展的硬编码。
术语说明 :                
SPI :Service Provider Interface 。
扩展点 :称 Dubbo 中被 @SPI 注解的 Interface 为一个扩展点。
扩展 :被 @SPI 注解的 Interface 的实现称为这个扩展点的一个扩展。






Dubbo SPI  约定


扩展点约定 :  扩展点必须是 Interface 类型 , 必须被 @SPI 注解 , 满足这两点才是一个扩展点。
扩展定义约定 :在 META-INF/services/$扩展点接口的全类名 , META-INF/dubbo/$扩展点接口的全类名 , META-INF/dubbo/internal/$扩展点接口的全类名 , 这些路径下定义的文件名称为 $扩展点接口的全类名 , 文件中以键值对的方式配置扩展点的扩展实现。例如文件 META-INF/dubbo/internal/com.alibaba.dubbo.common.extension.ExtensionFactory 中定义的扩展 :
adaptive=com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactoryspi=com.alibaba.dubbo.common.extension.factory.SpiExtensionFactoryspring=com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory


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






@SPI 、@Adaptive 、@Activate 作用


@SPI (注解在类上) :  @SPI 注解标识了接口是一个扩展点 , 属性 value 用来指定默认适配扩展点的名称。


@Activate (注解在类型和方法上) :  @Activate 注解在扩展点的实现类上 ,表示了一个扩展类被获取到的的条件,符合条件就被获取,不符合条件就不获取 ,根据 @Activate 中的 group 、 value 属性来过滤 。具体参考 ExtensionLoader 中的  getActivateExtension 函数。


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


package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol { public void destroy() { throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!"); }
public int getDefaultPort() { throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!"); }
public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws java.lang.Class { if (arg1 == null) throw new IllegalArgumentException("url == null"); com.alibaba.dubbo.common.URL url = arg1;
// 从 URL 中获取扩展名称 , "dubbo" 是从 ExtensionLoader 对象中的 cachedDefaultName // 属性获取到的 , cachedDefaultName 是扩展点上 @SPI 注解中 value 属性指定的 String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); if (extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
// 通过扩展名称获取扩展实例对象 , 调用扩展实例对象的相同方法 com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); return extension.refer(arg0, arg1); }
public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker { if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null"); if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null"); com.alibaba.dubbo.common.URL url = arg0.getUrl();
// 从 URL 中获取扩展名称 String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); if (extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
// 通过扩展名称获取扩展实例对象 , 调用扩展实例对象的相同方法 com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); return extension.export(arg0); }}


package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class ProxyFactory$Adpative implements com.alibaba.dubbo.rpc.ProxyFactory { public java.lang.Object getProxy(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker { if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null"); if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null"); com.alibaba.dubbo.common.URL url = arg0.getUrl();
// 从 URL 中获取扩展名称 String extName = url.getParameter("proxy", "javassist"); if (extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
// 通过扩展名称获取扩展实例对象 , 调用扩展实例对象的相同方法 com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName); return extension.getProxy(arg0); }
public com.alibaba.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, com.alibaba.dubbo.common.URL arg2) throws java.lang.Object { if (arg2 == null) throw new IllegalArgumentException("url == null"); com.alibaba.dubbo.common.URL url = arg2;
// 从 URL 中获取扩展名称 String extName = url.getParameter("proxy", "javassist"); if (extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
// 通过扩展名称获取扩展实例对象 , 调用扩展实例对象的相同方法 com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName); return extension.getInvoker(arg0, arg1, arg2); }}


这些代码都是模板代码 , 最核心的代码就只有一行 , 这行代码是去获取一个指定名称的扩展实例对象 :
ExtensionLoader.getExtensionLoader(Xxx.class).getExtension(extName);



在使用 dubbo 生成的源码时要注意 , 它生成的代码是有错误的, 比如 Protocol$Adpative 类的中的 refer 和 export 方法签名 throws 的不是 Protocol 接口中方法定义抛出的 RpcException , 而是 Class ,和 Invoker  。这个问题存在于 dubbo 2.5.4 之前的版本中,在 2.5.4 版本中修复了。





扩展加载器 ExtensionLoader 


扩展加载器绝对是一个核心组件了 ,它控制着 dubbo 内部所有扩展点的初始化、加载扩展的过程。这个类的源码是很有必要深入学习的。从 Dubbo 内核设计简图可以看到,现在的学习还没有接触到 dubbo 的内核。



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 是抛出的异常。

附:dubbo 开放的扩展点 :
com.alibaba.dubbo.cache.CacheFactorycom.alibaba.dubbo.common.compiler.Compilercom.alibaba.dubbo.common.extension.ExtensionFactorycom.alibaba.dubbo.common.logger.LoggerAdaptercom.alibaba.dubbo.common.serialize.Serializationcom.alibaba.dubbo.common.status.StatusCheckercom.alibaba.dubbo.common.store.DataStorecom.alibaba.dubbo.common.threadpool.ThreadPoolcom.alibaba.dubbo.container.Containercom.alibaba.dubbo.container.page.PageHandlercom.alibaba.dubbo.monitor.MonitorFactorycom.alibaba.dubbo.registry.RegistryFactorycom.alibaba.dubbo.remoting.Codec2com.alibaba.dubbo.remoting.Dispatchercom.alibaba.dubbo.remoting.exchange.Exchangercom.alibaba.dubbo.remoting.http.HttpBindercom.alibaba.dubbo.remoting.p2p.Networkercom.alibaba.dubbo.remoting.telnet.TelnetHandlercom.alibaba.dubbo.remoting.Transportercom.alibaba.dubbo.remoting.zookeeper.ZookeeperTransportercom.alibaba.dubbo.rpc.cluster.Clustercom.alibaba.dubbo.rpc.cluster.ConfiguratorFactorycom.alibaba.dubbo.rpc.cluster.LoadBalancecom.alibaba.dubbo.rpc.cluster.Mergercom.alibaba.dubbo.rpc.cluster.RouterFactorycom.alibaba.dubbo.rpc.Filtercom.alibaba.dubbo.rpc.InvokerListenercom.alibaba.dubbo.rpc.Protocolcom.alibaba.dubbo.rpc.protocol.thrift.ClassNameGeneratorcom.alibaba.dubbo.rpc.ProxyFactorycom.alibaba.dubbo.validation.Validation




本文分享自微信公众号 - 黑帽子技术(SNJYYNJY2020)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章