SPI简介
SPI 全称为 Service Provider Interface,是一种服务发现机制,刚好与java jdk的类加载机制进行了一个互补。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。如果大家想要学习 Dubbo 的源码,SPI 机制务必弄懂。接下来,我们先来了解一下 Java SPI 与 Dubbo SPI 的用法,然后再来分析 Dubbo SPI 的源码。
Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。
关于Java SPI
在了解Dubbo的SPI机制之前,我们先了解下Java提供的SPI (service provider interface) 机制,SPI是JDK内置的一种服务提供发现机制。目前市面上很多框架都用它来做服务的扩展发现。简单的说,它是一种动态替换发现的机制。
举个简单的例子,我们想在运行时动态给它添加实现,你只需要添加一个实现,然后把新的实现描述给JDK知道就行了。大家耳熟能详的如JDBC,日志框架都有用到。
实现 SPI 需要遵循的标准
我们如何去实现一个标准的 SPI 发现机制呢?其实很简单,只需要满足以下提交就行了 :
需要在 classpath 下创建一个目录,该目录命名必须是:META-INF/service
在该目录下创建一个 properties 文件,该文件需要满足以下几个条件 :
2.1 文件名必须是扩展的接口的全路径名称
2.2 文件内部描述的是该扩展接口的所有实现类
2.3 文件的编码格式是 UTF-8
通过 java.util.ServiceLoader 的加载机制来发现
SPI 的实际应用
SPI 在很多地方有应用,可能大家都没有关注,最常用的就是 JDBC 驱动,我们来看看是怎么应用的。
JDK 本身提供了数据访问的 api。在 java.sql 这个包里面 ,我们在连接数据库的时候,一定需要用到 java.sql.Driver 这个接口对吧。然后我好奇的去看了下 java.sql.Driver 的源码,发现 Driver 并没有实现,而是提供了一套标准的 api 接口。大家有兴趣可以去看看,因为我们在实际应用中用的比较多的是 mysql,所以我去 mysql 的包里面看到一个如下的目录结构
这个文件里面写的就是 mysql 的驱动实现。我恍然大悟,原来通过 SPI 机制把 java.sql.Driver 和 mysql 的驱动做了集成。这样 就达到了各个数据库厂商自己去实现数据库连接,jdk 本身不关心你怎么实现。
SPI 的缺点
JDK 标准的 SPI 会一次性加载实例化扩展点的所有实现,什么意思呢?就是如果你在 META-INF/service 下的文件里面加了 N 个实现类,那么 JDK 启动的时候都会一次性全部加载。那么如果有的扩展点实现初始化很耗时或者如果有些实现类并没有用到, 那么会很浪费资源
如果扩展点加载失败,会导致调用方报错,而且这个错误很难定位到是这个原因