[Switch] appreciated Dubbo SPI extension mechanism

EDITORIAL

    Recently contacted gRPC appreciate Although many RPC framework each have their own characteristics but the characteristics and functions they provide a lot of similarities, which indicates that they face the same problems caused by the distributed system. From around 2016, came into contact with dubbo, basic stay at the level of use, design and dubbo important to solve the problem have not studied the system, through the study of dubbo RPC and other similar products, systems, distributed learning system a common problem faced by road and solutions. 

Microkernel architecture

    Microkernel architecture (Microkernel architecture) mode is also known as plug-in architecture (Plugin architecture) mode. Originally integrated with the core components together are separated, the kernel provides a specific interface so that these components can be flexible access, these components operate at the core of management, but these components can separate development, changes (not to existing systems resulting in changes), provided that they meet the kernel interfaces can be. Typical examples of such, Eclipse, IDEA.

                       

Dubbo microkernel design

    According to my personal understanding of the micro-kernel design of Dubbo, and read the source code summary. Always the most visually intuitive, allowing the brain the fastest speed of one of the most intuitive understanding, to start an in-depth source details will only make people confused. Dubbo do not understand microkernel architecture design, then learning it will take a lot of detours.

                

    dubbo kernel extension is no sense, completely unaware of the existence of the expansion, the kernel code using specific extension will not be hard-coded.

    Terminology:

 SPI : Service Provider Interface 。

 Extension point: Dubbo are @SPI said annotated Interface is an extension point.

 Extended: The @SPI annotated Interface implementation is called an extension of this extension point.

Dubbo SPI convention

    Extension point agreement: extension point must be the Interface type, you must be @SPI annotations meet these two points is an extension point.

    扩展定义约定 : 在 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.AdaptiveExtensionFactory
spi=com.alibaba.dubbo.common.extension.factory.SpiExtensionFactory
spring=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.CacheFactory
com.alibaba.dubbo.common.compiler.Compiler
com.alibaba.dubbo.common.extension.ExtensionFactory
com.alibaba.dubbo.common.logger.LoggerAdapter
com.alibaba.dubbo.common.serialize.Serialization
com.alibaba.dubbo.common.status.StatusChecker
com.alibaba.dubbo.common.store.DataStore
com.alibaba.dubbo.common.threadpool.ThreadPool
com.alibaba.dubbo.container.Container
com.alibaba.dubbo.container.page.PageHandler
com.alibaba.dubbo.monitor.MonitorFactory
com.alibaba.dubbo.registry.RegistryFactory
com.alibaba.dubbo.remoting.Codec2
com.alibaba.dubbo.remoting.Dispatcher
com.alibaba.dubbo.remoting.exchange.Exchanger
com.alibaba.dubbo.remoting.http.HttpBinder
com.alibaba.dubbo.remoting.p2p.Networker
com.alibaba.dubbo.remoting.telnet.TelnetHandler
com.alibaba.dubbo.remoting.Transporter
com.alibaba.dubbo.remoting.zookeeper.ZookeeperTransporter
com.alibaba.dubbo.rpc.cluster.Cluster
com.alibaba.dubbo.rpc.cluster.ConfiguratorFactory
com.alibaba.dubbo.rpc.cluster.LoadBalance
com.alibaba.dubbo.rpc.cluster.Merger
com.alibaba.dubbo.rpc.cluster.RouterFactory
com.alibaba.dubbo.rpc.Filter
com.alibaba.dubbo.rpc.InvokerListener
com.alibaba.dubbo.rpc.Protocol
com.alibaba.dubbo.rpc.protocol.thrift.ClassNameGenerator
com.alibaba.dubbo.rpc.ProxyFactory
com.alibaba.dubbo.validation.Validation

 

原文链接:https://my.oschina.net/j4love/blog/1813040

Guess you like

Origin www.cnblogs.com/jmcui/p/10962292.html