dubbo基于SPI思想的实现

一、classload引出SPI

1、ClassLoader的简单介绍

Class的装载大体上可以分为加载类、连接类和初始化三个阶段,在这三个阶段中,所有的Class都是由ClassLoader进行加载的,然后Java虚拟机负责连接、初始化等操作.也就是说,无法通过ClassLoader去改变类的连接和初始化行为.

2、双亲委派模型

双亲委派模型好处

Java类随着它的类加载器一起具备了带有优先级的层次关系,保证java程序稳定运行

3、双亲委派模型的弊端

判断类是否加载的时候,应用类加载器会顺着双亲路径往上判断,直到启动类加载器.但是启动类加载器不会往下询问,这个委托路线是单向的,即顶层的类加载器,无法访问底层的类加载器所加载的类,如图

启动类加载器中的类为系统的核心类,比如,在系统类中,提供了一个接口,并且该接口还提供了一个工厂方法用于创建该接口的实例,但是该接口的实现类在应用层中,接口和工厂方法在启动类加载器中,就会出现工厂方法无法创建由应用类加载器加载的应用实例问题

拥有这样问题的组件很多,例如JNDI、JDBC、common-logging等。

4、Java SPI

Java中,把核心类(rt.jar)中提供外部服务,可由应用层自行实现的接口,这种方式成为spi.那我们看一下,在启动类加载器中,访问由应用类加载器实现spi接口的原理

Thread类中有两个方法:

使用这两个方法可以让classLoader处于线程上下文,使用这个线程上下文类加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载动作,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型

二、Dubbo 中基于SPI思想的实现

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

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

· JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。

· 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK标准的ScriptEngine,通过getName();获取脚本类型的名称,但如果RubyScriptEngine因为所依赖的jruby.jar不存在,导致RubyScriptEngine类加载失败,这个失败原因被吃掉了,和ruby对应不起来,当用户执行ruby脚本时,会报不支持ruby,而不是真正失败的原因。

· 增加了对扩展点IoC和AOP的支持,一个扩展点可以直接setter注入其它扩展点。

扩展点特性

扩展点自动包装:

自动Wrap扩展点的Wrapper类
ExtensionLoader会把加载扩展点时(通过扩展点配置文件中内容),如果该实现有拷贝构造函数,则判定为扩展点Wrapper类。

Wrapper类同样实现了扩展点接口(摘自dubbo文档)

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

Wrapper类内容:

package com.alibaba.xxx;

import com.alibaba.dubbo.rpc.Protocol;

public class XxxProtocolWrapper implemenets Protocol {

    Protocol impl;

 

    public XxxProtocol(Protocol protocol) { impl = protocol; }

 

    // 接口方法做一个操作后,再调用extension的方法

    public void refer() {

        //... 一些操作

        impl.refer();

        // ... 一些操作

    }

 

    // ...

}

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

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

通过 Wrapper 类可以把所有扩展点公共逻辑移至 Wrapper 中。新加的 Wrapper 在所有的扩展点上添加了逻辑,有些类似 AOP,即 Wrapper 代理了扩展点。使用的是装饰设计模式,装饰设计模式有很多弊端,有兴趣的可以看下装饰设计模式到动态代理的演变过程。

扩展点自动装配

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

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

接口类如下:

public interface CarMaker {

    Car makeCar();

}

public interface WheelMaker {

    Wheel makeWheel();

}

CarMaker 的一个实现类:

public class RaceCarMaker implemenets 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 的实现中要注入哪个。

扩展点自适应

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 implemenets 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 值,来决定代理到哪个实际扩展点。

参考文档:

https://www.jianshu.com/p/718acbda838c

http://dubbo.apache.org/books/dubbo-dev-book/SPI.html


猜你喜欢

转载自blog.csdn.net/h525483481/article/details/80761938
今日推荐