Dubbo进阶(五):Dubbo扩展点加载机制(上)

Dubbo的扩展点加载机制类似于JavaSPI加载机制,但是JavaSPI加载机制在查找具体某个实现的时候,只能通过遍历进行查找并会实例化所有实现类,因此对于实际不需要加载的扩展实现也会实例化,造成一定的内存浪费。Dubbo SPI加载机制可通过扩展点名称进行查找,避免实例化所有实现;同时,增加了对扩展点IoC和AOP的支持,一个扩展点实现可以注入其他扩展点实现并进行Wrapper包装。

在了解Dubbo SPI机制之前我们先了解一下JavaSPI机制。

Java SPI

以MySQL数据库驱动为例,来看一下Java SPI是如何实现的。

首先,MySQL软件驱动包在META-INF/services目录下创建了java.sql.Driver文件
在这里插入图片描述

java.sql.Driver文件如下,内容为具体实现类的全路径名

在这里插入图片描述
DriverManager类在被虚拟机加载时会运行如下代码:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

在这里插入图片描述

其中loadInitialDrivers()就是DriverManager使用SPI机制加载mysql声明的驱动,loadInitialDrivers()核心代码如下:

    private static void loadInitialDrivers() {
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
        // If the driver is packaged as a Service Provider, load it.
        // Get all the drivers through the classloader
        // exposed as a java.sql.Driver.class service.
        // ServiceLoader.load() replaces the sun.misc.Providers()

        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();

                /* Load these drivers, so that they can be instantiated.
                 * It may be the case that the driver class may not be there
                 * i.e. there may be a packaged driver with the service class
                 * as implementation of java.sql.Driver but the actual class
                 * may be missing. In that case a java.util.ServiceConfigurationError
                 * will be thrown at runtime by the VM trying to locate
                 * and load the service.
                 *
                 * Adding a try catch block to catch those runtime errors
                 * if driver not available in classpath but it's
                 * packaged as service and that service is there in classpath.
                 */
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });

        println("DriverManager.initialize: jdbc.drivers = " + drivers);

        if (drivers == null || drivers.equals("")) {
            return;
        }
        String[] driversList = drivers.split(":");
        println("number of Drivers:" + driversList.length);
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }

参照MySQL数据库驱动总结一下Java SPI具体的实现步骤:

  • 定义一个接口及对应的方法
  • 实现这个接口
  • META-INF/services下创建一个亿接口全路径命名的文件,如:java.sql.Driver
  • 文件内容为具体实现类的全路径名,如果有多个,就用分行符号分隔
  • 在代码中通过ServiceLoader来加载具体类的实现类

但是Java SPI也是有一些缺陷的

  • JDK自带的SPI会一次性实例化所有扩展点实现,如果扩展点不使用,那么会浪费资源
  • 在扩展点加载失败的情况下,JDK扩展点加载机制无法提供扩展点加载失败的真正原因
  • JDK自带SPI机制不支持IOC和AOP的功能
Dubbo SPI

相比Java SPIDubbo SPI做了一些改进和优化:

  • 相对于 Java SPI 一次性加载所有实现,Dubbo SPI 是按需加载,只加载需要使用的实现类
  • 更为详细的扩展加载失败信息
  • 增加了对扩展 IOC 和 AOP的支持

Dubbo SPI和Java SPI类似,需要在META-INF/dubbo/接口全限定名配置对应的SPI配置文件,文件内容为key=扩展点实现类的全路径名称,如果有多个实现,则用换行符分隔。其中,key会作为Dubbo SPI注解中的传入参数。另外,Dubbo SPI还兼容了Java SPI的配置路径和内容配置方式。在Dubbo启动的时候,会默认扫描这三个目录下的配置文件:META-INF/services、META-INF/dubbo/、META-INF/dubbo/internal,如下所示:

规范名 规范说明
SP配置文件路径 META-INF/services、META-INF/dubbo/、META-INF/dubbo/internal
SPI配置文件名称 全路径类名
文件内容格式 key=value,多个用换行符分隔
Dubbo SPI 示例,来自官网

Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下。

  • 定义一个Robot接口
@SPI
public interface Robot {
    void sayHello();
}
  • 接下来定义两个实现类,分别为 OptimusPrime 和 Bumblebee。
public class OptimusPrime implements Robot {
    
    @Override
    public void sayHello() {
        System.out.println("Hello, I am Optimus Prime.");
    }
}

public class Bumblebee implements Robot {

    @Override
    public void sayHello() {
        System.out.println("Hello, I am Bumblebee.");
    }
}
  • 接下来 META-INF/dubbo 文件夹下创建一个文件,名称为 Robot 的全限定名 org.apache.spi.Robot。文件内容为实现类的全限定的类名,如下:
optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee
  • 与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。另外,在测试 Dubbo SPI 时,需要在 Robot 接口上标注 @SPI 注解。
public class DubboSPITest {

    @Test
    public void sayHello() throws Exception {
        ExtensionLoader<Robot> extensionLoader = 
            ExtensionLoader.getExtensionLoader(Robot.class);
        Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
        optimusPrime.sayHello();
        Robot bumblebee = extensionLoader.getExtension("bumblebee");
        bumblebee.sayHello();
    }
}

测试结果如下:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/wangchengming1/article/details/105796145