zoukankan      html  css  js  c++  java
  • Dubbo的SPI机制

    SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。

    Dubbo SPI的改进

    Dubbo 的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。

    Dubbo 改进了 JDK 标准的 SPI 的以下问题:

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

    Dubbo SPI的约定

    在扩展类的 jar 包内,放置扩展点配置文件 META-INF/dubbo/接口全限定名,内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔。

    以扩展 Dubbo 的协议为例,在协议的实现 jar 包内放置文本文件:META-INF/dubbo/org.apache.dubbo.rpc.Protocol,内容为:

    xxx=com.alibaba.xxx.XxxProtocol
    

    实现类内容:

    package com.alibaba.xxx;
     
    import org.apache.dubbo.rpc.Protocol;
     
    public class XxxProtocol implements Protocol { 
        // ...
    }
    

    Dubbo 配置模块中,扩展点均有对应配置属性或标签,通过配置指定使用哪个扩展实现。比如:

    <dubbo:protocol name="xxx" />
    

    扩展点特性


    扩展点自动包装(AOP特性)

    自动包装扩展点的 Wrapper 类。ExtensionLoader 在加载扩展点时,如果加载到的扩展点有拷贝构造函数,则判定为扩展点 Wrapper 类。

    Wrapper类内容:

    package com.alibaba.xxx;
     
    import org.apache.dubbo.rpc.Protocol;
     
    public class XxxProtocolWrapper implements Protocol {
        Protocol impl;
     
        public XxxProtocolWrapper(Protocol protocol) { impl = protocol; }
     
        // 接口方法做一个操作后,再调用extension的方法
        public void refer() {
            //... 一些操作
            impl.refer();
            // ... 一些操作
        }
     
        // ...
    }
    

    Wrapper 类同样实现了扩展点接口,但是 Wrapper 不是扩展点的真正实现。它的用途主要是用于从 ExtensionLoader 返回扩展点时,包装在真正的扩展点实现外。即从 ExtensionLoader 中返回的实际上是 Wrapper 类的实例,Wrapper 持有了实际的扩展点实现类。

    扩展点的 Wrapper 类可以有多个,也可以根据需要新增。

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

    扩展点自动装配(IoC特性)

    加载扩展点时,自动注入依赖的扩展点。加载扩展点时,扩展点实现类的成员如果为其它扩展点类型,ExtensionLoader 在会自动注入依赖的扩展点。ExtensionLoader 通过扫描扩展点实现类的所有 setter 方法来判定其成员。即 ExtensionLoader 会执行扩展点的拼装操作。

    示例:有两个为扩展点 CarMaker(造车者)、WheelMaker (造轮者)

    接口类如下:

    public interface CarMaker {
        Car makeCar();
    }
     
    public interface WheelMaker {
        Wheel makeWheel();
    }
    

    CarMaker 的一个实现类:

    public class RaceCarMaker implements CarMaker {
        WheelMaker wheelMaker;
     
        public setWheelMaker(WheelMaker wheelMaker) {
            this.wheelMaker = wheelMaker;
        }
     
        public Car makeCar() {
            // ...
            Wheel wheel = wheelMaker.makeWheel();
            // ...
            return new RaceCar(wheel, ...);
        }
    }
    

    ExtensionLoader 加载 CarMaker 的扩展点实现 RaceCar 时,setWheelMaker 方法的 WheelMaker 也是扩展点则会注入 WheelMaker 的实现。

    这里带来另一个问题,ExtensionLoader 要注入依赖扩展点时,如何决定要注入依赖扩展点的哪个实现。在这个示例中,即是在多个WheelMaker 的实现中要注入哪个。

    这个问题在下面一点 扩展点自适应 中说明。

    扩展点自适应(Adaptive 特性)

    ExtensionLoader 注入的依赖扩展点是一个 Adaptive 实例,直到扩展点方法执行时才决定调用是一个扩展点实现。

    Dubbo 使用 URL 对象(包含了Key-Value)传递配置信息。

    扩展点方法调用会有URL参数(或是参数有URL成员)

    这样依赖的扩展点也可以从URL拿到配置信息,所有的扩展点自己定好配置的Key后,配置信息从URL上从最外层传入。URL在配置传递上即是一条总线。

    示例:有两个为扩展点 CarMakerWheelMaker

    接口类如下:

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

    CarMaker 的一个实现类:

    public class RaceCarMaker implements CarMaker {
        WheelMaker wheelMaker;
     
        public setWheelMaker(WheelMaker wheelMaker) {
            this.wheelMaker = wheelMaker;
        }
     
        public Car makeCar(URL url) {
            // ...
            Wheel wheel = wheelMaker.makeWheel(url);
            // ...
            return new RaceCar(wheel, ...);
        }
    }
    

    当上面执行

    // ...
    Wheel wheel = wheelMaker.makeWheel(url);
    // ...
    

    时,注入的 Adaptive 实例可以提取约定 Key 来决定使用哪个 WheelMaker 实现来调用对应实现的真正的 makeWheel 方法。如提取 wheel.type, key 即 url.get("wheel.type") 来决定 WheelMake 实现。Adaptive 实例的逻辑是固定,指定提取的 URL 的 Key,即可以代理真正的实现类上,可以动态生成。

    在 Dubbo 的 ExtensionLoader 的扩展点类对应的 Adaptive 实现是在加载扩展点里动态生成。指定提取的 URL 的 Key 通过 @Adaptive 注解在接口方法上提供。

    下面是 Dubbo 的 Transporter 扩展点的代码:

    public interface Transporter {
        @Adaptive({"server", "transport"})
        Server bind(URL url, ChannelHandler handler) throws RemotingException;
     
        @Adaptive({"client", "transport"})
        Client connect(URL url, ChannelHandler handler) throws RemotingException;
    }
    

    对于 bind() 方法,Adaptive 实现先查找 server key,如果该 Key 没有值则找 transport key 值,来决定代理到哪个实际扩展点。

    扩展点自动激活(Activate 特性)

    对于集合类扩展点,比如:Filter, InvokerListener, ExportListener, TelnetHandler, StatusChecker 等,可以同时加载多个实现,此时,可以用自动激活来简化配置,如:

    import org.apache.dubbo.common.extension.Activate;
    import org.apache.dubbo.rpc.Filter;
     
    @Activate // 无条件自动激活
    public class XxxFilter implements Filter {
        // ...
    }
    

    或:

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

    或:

    import org.apache.dubbo.common.extension.Activate;
    import org.apache.dubbo.rpc.Filter;
     
    @Activate(group = "provider", value = "xxx") // 只对提供方激活,group可选"provider"或"consumer"
    public class XxxFilter implements Filter {
        // ...
    }
    

    注意:这里的配置文件是放在你自己的 jar 包内,不是 dubbo 本身的 jar 包内,Dubbo 会全 ClassPath 扫描所有 jar 包内同名的这个文件,然后进行合并。
    注意:扩展点使用单一实例加载(请确保扩展实现的线程安全性),缓存在 ExtensionLoader。 中

    总结

    SPI机制是Dubbo的内核,这里实现了Dubbo自己的IoC和AOP等机制,在实现扩展自适应特性时,还用到了动态编译,可以说要学习Dubbo的源码,SPI机制务必要弄懂。

    说明:Dubbo官方文档写得很好,Dubbo源码分析系列的很多文章都是从官网摘抄的。官网传送门:https://dubbo.incubator.apache.org/zh-cn/docs/user/quick-start.html

  • 相关阅读:
    typescript 箭头表达式
    typescript 参数类型
    ts介绍
    pm2
    koa2安装
    linux 搭建ftp
    CENTOS6.5 安装 mysql5.6 以及搭建双主
    bzoj 3043 (差分序列运用)
    poj 3277 City Horizon
    NOI2015 程序自动分析
  • 原文地址:https://www.cnblogs.com/bluemilk/p/10308543.html
Copyright © 2011-2022 走看看