写点什么

dubbo 源码 v2.7 分析:SPI 机制

发布于: 2021 年 02 月 28 日
dubbo 源码 v2.7 分析:SPI机制

系列文章

dubbo 源码 v2.7 分析:结构、container 入口及线程模型


一 SPI 机制

1.1 定义

SPI 机制全称 Service Provider Interface,即为某个接口寻找服务实现的机制。

1.2 JDK 与 dubbo

常见的 SPI 实现,包括基于 JDK 和 dubbo 两种。JDBC 4.0 之后,就采用了 JDK 的 SPI 机制,不再使用 Class.forName 进行驱动加载。而 dubbo 在 JDK 的 SPI 之上做了优化,所以二者有所不同。

1.2.1 标准的 SPI 发现机制

需要在 classpath 下创建一个目录,该目录命名必须是:META-INF/service

在该目录下创建一个 properties 文件,该文件需要满足以下几个条件 :

2.1 文件名必须是扩展的接口的全路径名称

2.2 文件内部描述的是该扩展接口的所有实现类

2.3 文件的编码格式是 UTF-8

通过 java.util.ServiceLoader 的加载机制来发现

1.2.2 标准 SPI 的缺点

  1. JDK 标准的 SPI 会一次性加载实例化扩展点的所有实现,什么意思呢?就是如果你在 META-INF/service 下的文件里面加了 N 个实现类,那么 JDK 启动的时候都会一次性全部加载。那么如果有的扩展点实现初始化很耗时或者如果有些实现类并没有用到, 那么会很浪费资源

  2. 如果扩展点加载失败,会导致调用方报错,而且这个错误很难定位到是这个原因。

1.2.3 标准 SPI 使用示例-JDBC 的 DriverManager

JDK1.8 的 DriverManager 中,我们查看 DriverManager 源码,可以看到有静态初始化加载驱动的方法:

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); } } }
复制代码

从注释中可以看出,如果 spi 存在将使用 spi 方式完成提供的 Driver 的加载,在 run 方法内,查找具体的 provider,就是在 META-INF/services/***.Driver 文件中查找具体的实现。在找到具体的实现类的全限定名称之后,加载并初始化实现类。

二 Dubbo 与 JDK 标准 SPI 机制差异:

dubbo spi 也具有同样的功能,但是它更加的强大。在 dubbo 开发者文档中,spi 叫做扩展点

2.1 dubbo spi 增强

增强的地方有三点。

  • 不会一次性实例化所有实现,而只在需要时实例化

  • 扩展点加载失败的原因有更清楚的展示

  • 增加了 IoC 和 AOP 的支持

2.2 Dubbo 扩展点原理

在看 Dubbo SPI 的实现代码之前,先思考一个问题,所谓的扩展点,就是通过指定目录下配置一个对应接口的实现类,然后程序会进行查找和解析,找到对应的扩展点,那么这里就涉及到两个问题:


  1. 怎么解析

  2. 被加载的类如何存储和使用

我们通过上面的例子可以知道,我们是通过下面这个代码去加载扩展点的:

ExtensionLoader.getExtensionLoader.getExtension
复制代码


我们从这段代码着手,去看看到底做了什么事情,能够通过这样一段代码实现扩展协议的查找和加载。

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {        if (type == null) {            throw new IllegalArgumentException("Extension type == null");        }        if (!type.isInterface()) {            throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");        }        if (!withExtensionAnnotation(type)) {            throw new IllegalArgumentException("Extension type (" + type +                    ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");        }
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader;}12345678910111213141516171819
复制代码


从上面代码可以看出, 先会去检查我们想要的扩展点是否已经存在于 EXTENSION_LOADERS 这个缓存中,如果存在则直接返回,否则新创建一个 ExtensionLoader。


private ExtensionLoader(Class<?> type) {        this.type = type;        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());    }
复制代码


如果当前的 type=ExtensionFactory,那么 objectFactory=null, 否则会创建一个自适应扩展点给到 objectFacotry,目前来说具 体做什么咱们先不关心,现在只要知道 objectFactory 在这里赋值了,并且是返回一个 AdaptiveExtension(). 这个暂时不展开,后面再分析。


发布于: 2021 年 02 月 28 日阅读数: 29
用户头像

磨炼中成长,痛苦中前行 2017.10.22 加入

微信公众号【程序员架构进阶】。多年项目实践,架构设计经验。曲折中向前,分享经验和教训

评论

发布
暂无评论
dubbo 源码 v2.7 分析:SPI机制