共计 2507 个字符,预计需要花费 7 分钟才能阅读完成。
1. SPI 介绍
基于接口编程时,两个模块之间须要接口进行交互
如果这个接口在 被调用方 中,则属于 API
如果这个接口在 调用方 中,则属于 SPI
API,Application Programming Interface,利用程序接口
SPI,Service Provider Interface,服务提供接口,它是一种服务发现机制
在 JDK 的 java.util 包中有一个 ServiceLoader 类,它能够通过配置文件加载指定的 service provider
被调用方 提供了服务接口及其实现后,只须要在 META-INF/services/ 目录中创立一个以 接口全名 命名的文件,而后在文件中列出该接口的 具体实现类的全名
调用方 就能够应用 ServiceLoader 来加载到这些实现类,从而实现性能
2. SPI 示例
只看介绍的话有点绕,这里用一个简略示例,来演示 SPI 的应用
首先,申明一个接口:com.example.summerboot.spi.Animal,外面申明办法 sound:
package com.example.summerboot.spi; | |
public interface Animal {void sound(); | |
} |
而后,实现两个 Animal 接口的类及其办法:
public class Cat implements Animal { | |
@Override | |
public void sound() {System.out.println("miao ~"); | |
} | |
} | |
public class Dog implements Animal { | |
@Override | |
public void sound() {System.out.println("wang! wang!"); | |
} | |
} |
再而后,在 META-INF/services 中创立一个文件,命名为com.example.summerboot.spi.Animal,即接口全名
并在其中列出两个实现类的全名
最初,应用 ServiceLoader 的 load 办法,来加载这两个实现类,并调用其 sound 办法:
public class SpiTest {public static void main(String[] args) {ServiceLoader<Animal> load = ServiceLoader.load(Animal.class); | |
for (Animal animal : load) {animal.sound(); | |
} | |
} | |
} |
运行后果:
值得注意的是,ServiceLoader 能够加载到所有 jar 包中 META-INF/services 下接口的实现类
比方,如果咱们把上边的代码批改成这样:
public class SpiTest {public static void main(String[] args) {ServiceLoader<Animal> load = ServiceLoader.load(Animal.class); | |
for (Animal animal : load) {animal.sound(); | |
} | |
ServiceLoader<Driver> drivers = ServiceLoader.load(Driver.class); | |
for (Driver driver : drivers) {System.out.println(driver.getClass().getName()); | |
} | |
} | |
} |
运行后果:
能够看到,加载 Driver 接口时,把 mysql 和 druid 中的 Driver 实现类也加载了进来
别离关上 mysql 和 druid 的 jar 包,果然能够看到其配置的 META-INF/services 下的 java.sql.Driver 文件
3. SPI 在 DriverManager 中的利用
在应用 JDBC 连贯数据库之前,须要应用 DriverManager 获取连贯,那它是怎么确定应该加载哪个驱动呢?
答案就是,它都加载,而后遍历调用所有驱动的 connect 办法,而后由各个驱动来确定 URL 是不是本人所反对的
相似于这样的代码:
if (!ConnectionUrl.acceptsUrl(url)) { | |
/* | |
* According to JDBC spec: | |
* The driver should return "null" if it realizes it is the wrong kind of driver to connect to the given URL. This will be common, as when the | |
* JDBC driver manager is asked to connect to a given URL it passes the URL to each loaded driver in turn. | |
*/ | |
return null; | |
} |
那 DriverManager 又是怎么进行驱动加载的呢?
拿 mysql 来阐明:
首先,DriverManager 中有一段动态代码段用来加载并初始化驱动
static {loadInitialDrivers(); | |
println("JDBC DriverManager initialized"); | |
} |
在 loadInitialDrivers 办法中,会看到这样的代码
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); | |
Iterator<Driver> driversIterator = loadedDrivers.iterator(); |
可见 DriverManager 也是应用了 SPI,应用 ServiceLoader 把写到配置文件里的 Driver 都加载了进来。
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); | |
} | |
} |
参考鸣谢
Java SPI 思维:https://zhuanlan.zhihu.com/p/…