dubboSPI和jdkSPI的区别

dubboSPI和jdkSPI的区别

什么是SPI

SPI(Service Provider Interface)。Java在语言层面为我们提供了一种方便地创建可扩展应用的途径。SPI提供了一种JVM级别的服务发现机制,我们只需要按照SPI的要求,在jar包中进行适当的配置,jvm就会在运行时通过懒加载,帮我们找到所需的服务并加载。如果我们一直不使用某个服务,那么它不会被加载,一定程度上避免了资源的浪费。

JDK SPI最常见的例子就是java.sql.DriverManager中加载sql驱动的例子。

Java SPI

JDK自带的SPI的类是:java.util.ServiceLoader
使用方法以JDBC为例:

        // java.sql.DriverManager 首先在静态代码块中初始化全部懒加载进来
        // 首先拿到迭代器,而这个迭代器中,其实是拿一个扫描一个provider的 懒加载
        ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
        Iterator<Driver> driversIterator = loadedDrivers.iterator();
        // 以上代码还没有实例化过任何 Driver provider,以下代码,DriverManager才开始自动加载
        try {
            while (driversIterator.hasNext()) {
                driversIterator.next();
            }
        } catch (Throwable t) {
            // Do nothing
        }
        return null;

懒加载如下图所示,在load()了ServiceLoader后,经过参数检查 直接开始清空provider的缓存,然后重新获取provider。先查询已经缓存的provider有没有,如果没有再去遍历拿到新的provider。

而在加载过上述Driver provider之后,就会执行他的静态代码块,以DruidDriver为例具体代码如下所示:

    static {
        AccessController.doPrivileged(new PrivilegedAction<Object>() {
            public Object run() {
                DruidDriver.registerDriver(DruidDriver.instance);
                return null;
            }
        });
    }

 public static boolean registerDriver(Driver driver) {
        try {
            DriverManager.registerDriver(driver);

            try {
                MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
                ObjectName objectName = new ObjectName("com.alibaba.druid:type=DruidDriver");
                if (!mbeanServer.isRegistered(objectName)) {
                    mbeanServer.registerMBean(instance, objectName);
                }
            } catch (Throwable var3) {
                if (LOG == null) {
                    LOG = LogFactory.getLog(DruidDriver.class);
                }

                LOG.error("register druid-driver mbean error", var3);
            }

            return true;
        } catch (Exception var4) {
            if (LOG == null) {
                LOG = LogFactory.getLog(DruidDriver.class);
            }

            LOG.error("registerDriver error", var4);
            return false;
        }
    }

他相当于把 自己实例化后,又交给了DriverManager控制。
以上就是 jdk自带的SPI。

Dubbo SPI

Java SPI 在查找扩展实现类的时候遍历 SPI 的配置文件并且将实现类全部实例化,假设一个实现类初始化过程比较消耗资源且耗时,但是你的代码里面又用不上它,这就产生了资源的浪费。
所以说 Java SPI 无法按需加载实现类。
因此 Dubbo 就自己实现了一个 SPI,让我们想一下按需加载的话首先你得给个名字,通过名字去文件里面找到对应的实现类全限定名然后加载实例化即可。
Dubbo 就是这样设计的,配置文件里面存放的是键值对,并且 Dubbo SPI 除了可以按需加载实现类之外,增加了 IOC 和 AOP 的特性,还有个自适应扩展机制。

使用

/**
 * @author luyuanzhe
 * @date 2022-03-16 22:49
 * 创建SPI的接口
 */
@SPI
public interface Driver {
    /**
     * 注册
     */
    void  register();
}
// resources/META-INF/dubbo/com.didi.ssozh.lab.biz.myspi.Driver
// first=com.didi.ssozh.lab.biz.myspi.impl.FirstDriverImpl
/**
 * @author luyuanzhe
 * @date 2022-03-16 22:50
 * SPI的实现类
 */
public class FirstDriverImpl implements Driver {
    @Override
    public void register() {
        System.out.println("FirstDriverImpl register");
    }
}

    @Test
    public void test01(){
        ExtensionLoader<Driver> driverExtensionLoader = ExtensionLoader.getExtensionLoader(Driver.class);
        Driver firstDriver = driverExtensionLoader.getExtension("first");
        firstDriver.register();

    }

原理

我们先来看一下 Dubbo 对配置文件目录的约定,不同于 Java SPI ,Dubbo 分为了三类目录。

META-INF/services/ 目录:该目录下的 SPI 配置文件是为了用来兼容 Java SPI 。
META-INF/dubbo/ 目录:该目录存放用户自定义的 SPI 配置文件。
META-INF/dubbo/internal/ 目录:该目录存放 Dubbo 内部使用的 SPI 配置文件

AOP

通过打断点 可以看到在org.apache.dubbo.common.extension.ExtensionLoader#createExtension的时候会使用wrap的构造函数进行构造,最后给出去一个wrapper对象。

其中wrapper的顺序是:

driverWrapper01=com.didi.ssozh.lab.biz.myspi.impl.DriverWrapper01
first=com.didi.ssozh.lab.biz.myspi.impl.FirstDriverImpl
second=com.didi.ssozh.lab.biz.myspi.impl.SecondDriverImpl
driverWrapper02=com.didi.ssozh.lab.biz.myspi.impl.DriverWrapper02
/**
这个方法前面的公共部分01
这个方法前面的公共部分02
FirstDriverImpl register
这个方法后面的wrapper02
这个方法后面的wrapper01
**/

IOC

上图中。返回wrapper的Driver实现类的是this.injectExtension方法。

对比和总结

如果使用过就可以很容易比较出区别:

  1. 【按需加载】从使用上来说,java的是resources中的name是 provider,里面放了全部的实现类,而dubbo的则是注释@SPI且name在resource是provider,而里面的内容则是k=v的形式放了实现类的名字和实现类的类别。可以做到有选择的加载。好处就是可以选择性的加载,而不是java的懒加载(按顺序的)。
  2. 增加了 IOC 和 AOP 的特性
  3. 还有个自适应扩展机制

cite:https://blog.csdn.net/qq_35190492/article/details/108256452

posted @ 2022-03-16 20:58  SsoZh  阅读(259)  评论(0编辑  收藏  举报