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/...