Dubbo如何经过SPI提升框架的可扩展性?

介绍

最近看了一下Dubbo的源码,国人写的框架和国外的果真是两种不一样的风格,Dubbo的源码仍是比较清晰容易懂的。Spring框架一个Bean的初始化过程就能绕死在源码中.java

Dubbo的架构是基于分层来设计的,每层执行固定的功能,上层依赖下层,下层的改变对上层不可见,每层都是能够被替换的组件
git


Service和Config为API接口层,让Dubbo使用者方便的发布和引用服务
其余各层均为SPI层,意味着每层都是组件化的,能够被替换

例如,注册中心能够用Redis,Zookeeper。传输协议能够用dubbo,rmi,hessian等。
网络通讯能够用mina,netty。序列化能够用fastjson,hessian2,java原生的方式等github

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样能够在运行时,动态为接口替换实现类。正所以特性,咱们能够很容易的经过 SPI 机制为咱们的程序提供拓展功能web

那么Dubbo的SPI是怎么实现的呢?先来了解一下Java SPIspring

Java SPI

Java SPI是经过策略模式实现的,一个接口提供多个实现类,而使用哪一个实现类不在程序中肯定,而是配置文件配置的,具体步骤以下apache

  1. 定义接口及其对应的实现类json

  2. 在META-INF/services目录下建立以接口全路径命名的文件微信

  3. 文件内容为实现类的全路径名网络

  4. 在代码中经过java.util.ServiceLoader#load加载具体的实现类架构

写个Demo演示一下

public interface Car {

    void getBrand();
}

public class BenzCar implements Car {

    @Override
    public void getBrand() {
        System.out.println("benz");
    }
}

public class BMWCar implements Car {

    @Override
    public void getBrand() {
        System.out.println("bmw");
    }
}

org.apache.dubbo.Car的内容以下

org.apache.dubbo.BenzCar
org.apache.dubbo.BMWCar

测试类

public class JavaSpiDemo {

    public static void main(String[] args) {
        ServiceLoader<Car> carServiceLoader = ServiceLoader.load(Car.class);
        // benz
        // bmw
        carServiceLoader.forEach(Car::getBrand);
    }
}

Dubbo SPI

用Dubbo SPI将上面的例子改造一下

@SPI
public interface Car {

    void getBrand();
}

public class BenzCar implements Car {

    @Override
    public void getBrand() {
        System.out.println("benz");
    }
}

public class BMWCar implements Car {
    @Override
    public void getBrand() {
        System.out.println("bmw");
    }
}

org.apache.dubbo.quickstart.Car的内容以下

benz=org.apache.dubbo.quickstart.BenzCar
bmw=org.apache.dubbo.quickstart.BMWCar

测试类

public class DubboSpiDemo {

    public static void main(String[] args) {
        ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class);
        Car car = extensionLoader.getExtension("benz");
        car.getBrand();
    }
}

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {

    String value() default "";

}

@SPI标记接口是一个Dubbo SPI接口,便是一个扩展点,value属性能够指定默认实现

Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了加强,使其可以更好的知足需求。Dubbo SPI的优势以下

  1. JDK标准的SPI会一次性实例化扩展点的全部实现。而Dubbo SPI能实现按需加载

  2. Dubbo SPI增长了对扩展点Ioc和Aop的支持

Dubbo SPI的实现步骤以下

  1. 定义接口及其对应的实现类,接口上加@SPI注解,代表这是一个扩展类

  2. 在META-INF/services目录下建立以接口全路径命名的文件

  3. 文件内容为实现类的全路径名

  4. 在代码中经过ExtensionLoader加载具体的实现类

Dubbo SPI 扩展点的特性

自动包装

扩展类的构造函数是一个扩展点,则认为这个类是一个Wrapper类,即AOP

用例子演示一下

@SPI
public interface Car {

    void getBrand();
}

public class BenzCar implements Car {
    @Override
    public void getBrand() {
        System.out.println("benz");
    }
}

public class CarWrapper implements Car {

    private Car car;

    public CarWrapper(Car car) {
        this.car = car;
    }

    @Override
    public void getBrand() {
        System.out.println("start");
        car.getBrand();
        System.out.println("end");
    }
}

org.apache.dubbo.aop.Car内容以下(resources\META-INF\services目录下)

benz=org.apache.dubbo.aop.BenzCar
org.apache.dubbo.aop.CarWrapper

测试类

public class DubboSpiAopDemo {

    public static void main(String[] args) {
        ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class);
        Car car = extensionLoader.getExtension("benz");
        // start
        // benz
        // end
        car.getBrand();
    }
}

BenzCar是一个扩展类,CarWrapper是一个包装类,当获取BenzCar的时候实际获取的是被CarWrapper包装后的对象,相似代理模式

自动加载

若是一个扩展类是另外一个扩展类的成员变量,而且拥有set方法,框架会自动注入这个扩展点的实例,即IOC。先定义2个扩展点

org.apache.dubbo.ioc.Car(resources\META-INF\services目录下)

benz=org.apache.dubbo.ioc.BenzCar

org.apache.dubbo.ioc.Wheel(resources\META-INF\services目录下)

benz=org.apache.dubbo.ioc.BenzWheel
@SPI
public interface Wheel {

    void getBrandByUrl();
}

public class BenzWheel implements Wheel {

    @Override
    public void getBrandByUrl() {
        System.out.println("benzWheel");
    }
}

@SPI
public interface Car {

    void getBrandByUrl();
}

public class BenzCar implements Car {

    private Wheel wheel;

    public void setWheel(Wheel wheel) {
        this.wheel = wheel;
    }

    @Override
    public void getBrandByUrl() {
        System.out.println("benzCar");
        wheel.getBrandByUrl();
    }
}

测试demo

public class DubboSpiIocDemo {

    public static void main(String[] args) {
        ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class);
        Car car = extensionLoader.getExtension("benz");
        car.getBrandByUrl();
    }
}

我跑这个代码的时候直接报异常,看了一下官网才发现dubbo是能够注入接口的实现的,但不像spring那么智能,
dubbo必须用URL(相似总线)来指定扩展类对应的实现类.。这就不得不提到@Adaptive注解了

自适应

使用@Adaptive注解,动态的经过URL中的参数来肯定要使用哪一个具体的实现类

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {

    String[] value() default {};

}

@SPI
public interface Wheel {

    @Adaptive("wheel")
    void getBrandByUrl(URL url);
}

public class BenzWheel implements Wheel {

    @Override
    public void getBrandByUrl(URL url) {
        System.out.println("benzWheel");
    }
}

@SPI
public interface Car {

    void getBrandByUrl(URL url);
}

public class BenzCar implements Car {

    // 这个里面存的是代理对象
    private Wheel wheel;

    public void setWheel(Wheel wheel) {
        this.wheel = wheel;
    }

    @Override
    public void getBrandByUrl(URL url) {
        System.out.println("benzCar");
        // 代理类根据URL找到实现类,而后再调用实现类
        wheel.getBrandByUrl(url);
    }
}

public class DubboSpiIocDemo {

    public static void main(String[] args) {
        ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class);
        Car car = extensionLoader.getExtension("benz");
        Map<String, String> map = new HashMap<>();
        // 指定wheel的实现类为benz
        map.put("wheel""benz");
        URL url = new URL(""""1, map);
        // benzCar
        // benzWheel
        car.getBrandByUrl(url);
    }
}

能够看到BenzCar对象成功注入了BenzWheel。BenzCar中其实注入的是BenzWheel的代码对象,这个代理对象会根据@Adaptive("wheel")获取到wheel,而后从url中找到key为wheel的值,这个值即为实现类对应的key。

上面的注释提到BenzCar里面注入的Wheel实际上是一个代理对象(框架帮咱们生成),在代理对象中根据url找到相应的实现类,而后调用实现类。

由于代理对象是框架在运行过程当中帮咱们生成的,没有文件能够查看,因此用Arthas来查看一下生成的代理类

curl -O https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar
# 根据前面的序号选择进入的进程,而后执行下面的命令
jad org.apache.dubbo.adaptive.Wheel$Adaptive

生成的Wheel

package org.apache.dubbo.adaptive;

import org.apache.dubbo.adaptive.Wheel;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;

public class Wheel$Adaptive
implements Wheel 
{
    public void getBrandByUrl(URL uRL) {
        if (uRL == null) {
            throw new IllegalArgumentException("url == null");
        }
        URL uRL2 = uRL;
        String string = uRL2.getParameter("wheel");
        if (string == null) {
            throw new IllegalStateException(new StringBuffer().append("Failed to get extension (org.apache.dubbo.adaptive.Wheel) name from url (").append(uRL2.toString()).append(") use keys([wheel])").toString());
        }
        Wheel wheel = (Wheel)ExtensionLoader.getExtensionLoader(Wheel.class).getExtension(string);
        wheel.getBrandByUrl(uRL);
    }
}

@Adaptive能够标记在类上或者方法上

标记在类上:将该实现类直接做为默认实现,再也不自动生成代码
标记在方法上:经过参数动态得到实现类,好比上面的例子

用源码演示一下用在类上的@Adaptiv,Dubbo为自适应扩展点生成代码,如咱们上面的WheelAdaptive,但生成的代码还须要编译才能生成class文件。咱们能够用JavassistCompiler(默认的)或者JdkCompiler来编译(须要配置),这个小小的功能就用到了@Adaptive

若是想用JdkCompiler须要作以下配置

<dubbo:application compiler="jdk" />

Compiler类图以下

@SPI("javassist")
public interface Compiler {

    Class<?> compile(String code, ClassLoader classLoader);

}

Compiler用@SPI指定了默认实现类为javassist

源码中获取Compiler调用了以下方法

org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();

getAdaptiveExtension()会获取自适应扩展类,那么这个自适应扩展类是谁呢?

是AdaptiveCompiler,由于类上有@Adaptive注解

@Adaptive
public class AdaptiveCompiler implements Compiler {

    private static volatile String DEFAULT_COMPILER;

    public static void setDefaultCompiler(String compiler) {
        DEFAULT_COMPILER = compiler;
    }

    /**
     * 获取对应的Compiler,并调用compile作编译
     * 用户设置了compiler,就用设置了的,否则就用默认的
     */

    @Override
    public Class<?> compile(String code, ClassLoader classLoader) {
        Compiler compiler;
        ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
        String name = DEFAULT_COMPILER; // copy reference
        if (name != null && name.length() > 0) {
            // 用用户设置的
            compiler = loader.getExtension(name);
        } else {
            // 用默认的
            compiler = loader.getDefaultExtension();
        }
        return compiler.compile(code, classLoader);
    }

}

从compile方法能够看到,若是用户设置了编译方式,则用用户设置的,若是没有设置则用默认的,即JavassistCompiler

自动激活

使用@Activate注解,能够标记对应的扩展点默认被激活使用

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {

    // 所属组,例如消费端,服务端
    String[] group() default {};

    // URL中包含属性名为value的键值对,过滤器才处于激活状态
    String[] value() default {};

    // 指定执行顺序,before指定的过滤器在该过滤器以前执行
    @Deprecated
    String[] before() default {};

    // 指定执行顺序,after指定的过滤器在该过滤器以后执行
    @Deprecated
    String[] after() default {};

    // 指定执行顺序,值越小,越先执行
    int order() default 0;
}

能够经过指定group或者value,在不一样条件下获取自动激活的扩展点。before,after,order是用来排序的,感受一个order参数就能够搞定排序的功能,因此官方把before,after标记为@Deprecated

Dubbo Filter就是基于这个来实现的。Dubbo Filter是Dubbo可扩展性的一个体现,能够在调用过程当中对请求进行进行加强

我写个demo演示一下这个自动激活是怎么工做的

@SPI
public interface MyFilter 
    void filter();
}

consumer组能激活这个filter

@Activate(group = {"consumer"})
public class MyConsumerFilter implements MyFilter {
    @Override
    public void filter() {

    }
}

provider组能激活这个filter

@Activate(group = {"provider"})
public class MyProviderFilter implements MyFilter {
    @Override
    public void filter() {

    }
}

consumer组和provide组都能激活这个filter

@Activate(group = {"consumer""provider"})
public class MyLogFilter implements MyFilter {
    @Override
    public void filter() {

    }
}

consumer组和provide组都能激活这个filter,同时url中指定key的value为cache

@Activate(group = {"consumer""provider"}, value = "cache")
public class MyCacheFilter implements MyFilter {
    @Override
    public void filter() {

    }
}

测试类以下
getActivateExtension有3个参数,依次为url, key, group

public class ActivateDemo {

    public static void main(String[] args) {
        ExtensionLoader<MyFilter> extensionLoader = ExtensionLoader.getExtensionLoader(MyFilter.class);
        // url中没有参数
        URL url = URL.valueOf("test://localhost");
        List<MyFilter> allFilterList = extensionLoader.getActivateExtension(url, ""null);
        /**
         * org.apache.dubbo.activate.MyConsumerFilter@53e25b76
         * org.apache.dubbo.activate.MyProviderFilter@73a8dfcc
         * org.apache.dubbo.activate.MyLogFilter@ea30797
         *
         * 不指定组则全部的Filter都被激活
         */

        allFilterList.forEach(item -> System.out.println(item));
        System.out.println();

        List<MyFilter> consumerFilterList = extensionLoader.getActivateExtension(url, """consumer");
        /**
         * org.apache.dubbo.activate.MyConsumerFilter@53e25b76
         * org.apache.dubbo.activate.MyLogFilter@ea30797
         *
         * 指定consumer组,则只有consumer组的Filter被激活
         */

        consumerFilterList.forEach(item -> System.out.println(item));
        System.out.println();

        // url中有参数myfilter
        url = URL.valueOf("test://localhost?myfilter=cache");
        List<MyFilter> customerFilter = extensionLoader.getActivateExtension(url, "myfilter""consumer");
        /**
         * org.apache.dubbo.activate.MyConsumerFilter@53e25b76
         * org.apache.dubbo.activate.MyLogFilter@ea30797
         * org.apache.dubbo.activate.MyCacheFilter@aec6354
         *
         * 指定key在consumer组的基础上,MyCacheFilter被激活
         */

        customerFilter.forEach(item -> System.out.println(item));
        System.out.println();
    }
}

总结一下就是,getActivateExtension不指定组就是激活全部的Filter,指定组则激活指定组的Filter。指定key则从Url中根据key取到对应的value,假设为cache,而后把@Activate注解中value=cache的Filter激活

即group用来筛选,value用来追加,Dubbo Filter就是靠这个属性激活不一样的Filter的

ExtensionLoader的工做原理

ExtensionLoader是整个Dubbo SPI的主要实现类,有以下三个重要方法,搞懂这3个方法基本上就搞懂Dubbo SPI了。

加载扩展类的三种方法以下

  1. getExtension(),获取普通扩展类

  2. getAdaptiveExtension(),获取自适应扩展类

  3. getActivateExtension(),获取自动激活的扩展类

getExtension()上面的例子中已经有了。自适应的特性上面已经演示过了,当获取Wheel的实现类是框架会调用getAdaptiveExtension()方法。

代码就不放了,这3个方法的执行过程仍是比较简单的,若是你有看不懂的,能够看我给源码加的注释。

https://github.com/erlieStar/dubbo-analysis

理解了Dubbo SPI你应该就把Dubbo搞懂一半了,剩下就是一些服务导出,服务引入,服务调用的过程了

欢迎关注


有帮助?点赞!转发!

本文分享自微信公众号 - Java识堂(erlieStar)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索