java spi机制和dubbo spi机制的详解和底层原理

java spi 机制

什么是java spi机制?

SPI全称Service Provider Interface ,是java提供的一个功能,用于加载接口在本地的第三方实现,常用于框架的拓展和可替换组件的实现。

spi的核心思想是解耦,比如说 框架里面定义了一个LoadBalance接口,是用于实现负载均衡的接口, 但是具体的负载均衡算法的实现有许多种,比如说随机,轮询,一致性哈希,最小连接数等等。 然后比如说你使用了这个框架,但是要根据你的业务场景使用一个新的负载均衡算法。框架本身并不支持,怎么办?

如果这个框架内部的LoadBalance接口使用了spi机制,那么你就很容易拓展这个功能。

举个例子
具体步骤看如下:
定义接口CarInterface,里面是一个获取车的颜色的方法。

public interface CarInterface {
    void getColor();
}

我们需要在resources文件夹里 建立一个META-INF文件夹,然后再建立一个services文件夹。
里面以接口的全路径命名一个文件 ,即包路径

java spi机制和dubbo spi机制的详解和底层原理_第1张图片

文件内部写上实现类的全路径名
java spi机制和dubbo spi机制的详解和底层原理_第2张图片

实现类的代码如下


public class BlackCar implements CarInterface {
    @Override
    public void getColor() {
        System.out.println("black");
    }
}

public class redCar implements CarInterface {
    @Override
    public void getColor() {
        System.out.println("red");
    }
}


main方法里面 去调用

public class CarDemo {
    public static void main(String[] args) {
    
    //ServiceLoader 用于加载CarInterface接口的实现 返回一个iterator
   
        ServiceLoader serviceLoader=ServiceLoader.load(CarInterface.class);
        Iterator iterator=serviceLoader.iterator();

        while (iterator.hasNext()){
            CarInterface carInterface=iterator.next();
            carInterface.getColor();
        }

    }
}

ServiceLoader的作用是去查找CarInterface接口的实现类,查到之后,就会创建这个接口的实现类对象, 并返回一个列表。

这个列表内就有CarInterface接口在META-INF services 文件夹里的同名文件内部写的实现类的对象实例。

输出如下:
java spi机制和dubbo spi机制的详解和底层原理_第3张图片

那么回到之前的那个问题,假设这个是个框架, 如果我要拓展接口的实现,新增我自己的实现, 只需要创建一个新的接口的实现类,然后在META-INF的service文件夹里面,创建对应的配置文件和填写实现类的路径即可。
框架内部就会使用ServiceLoader去加载我的实现类。

最常见的,可以看mysql的驱动jar包里面的MATA-INF文件夹里面同样有这样的配置,因此JDBC加载不同类型数据库的驱动,也是用了同样的原理。

ServiceLoader的原理:
读取META-INF文件夹内的services文件
然后解析文件,找到对应的实现类的路径
通过Class.forName加载进来
newInstance()反射创建对象
并存到缓存和列表里面去

public final class ServiceLoader implements Iterable{
private static final String PREFIX = "META-INF/services/";

    // 代表被加载的类或者接口
    private final Class service;

    // 用于定位,加载和实例化providers的类加载器
    private final ClassLoader loader;

    // 创建ServiceLoader时采用的访问控制上下文
    private final AccessControlContext acc;

    // 缓存providers,按实例化的顺序排列
    private LinkedHashMap providers = new LinkedHashMap<>();

    // 懒查找迭代器
    private LazyIterator lookupIterator;
  
    ......
}
private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class c = null;
            try {
            //加载实现类
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
            //创建对象实例
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }

这里,java spi机制的优点是解耦,但是缺点也有,比如说,不能指定获取哪个实现类,写在配置里面的都会被加载进来。
其次,如果我的实现类里面, 依赖了其他类,不能支持依赖注入,同理,也不支持切面,如果要给多个现实类都加上相同的功能的话,要修改原来的代码。

因此,在dubbo spi中,对这三个缺点进行了优化。

首先是,支持指定获取实现类

首先是,支持切面

首先是,支持依赖注入

你可能感兴趣的:(dubbo源码分析)