转载自:Java SPI - sincosmos | Tech Blog
理解:将抽象与实现解耦
什么是 Java SPI
我们对 API 都比较熟悉,项目常见的用法是使用 maven 引入相关的第三方依赖,然后通过被依赖的包提供的 API 来使用其功能。
参照上图,API 的层次关系中,接口的定义和具体实现都在实现方。
而在 SPI 的层级关系中,接口的定义在调用方,具体实现在实现方。例如 JDBC 的的类结构中,用户程序作为调用方调用数据驱动类(java.sql)连接数据库,各种数据厂商(Oracle, mysql)作为实现方实现自己的数据库连接驱动。而为了规范数据库连接的操作,减少用户程序连接不同数据库的时候的复杂度,jdk 中定义了数据库驱动的标准接口,厂商只要按照规范实现自己的驱动,用户程序就能在无感知的情况使用不同的数据库,此时驱动的接口定义就在调用方,是典型的 SPI 型调用。
无论是 API 调用还是 SPI 调用,调用方都要获得实现方的实例。SPI 是借助 jdk 提供的 ServiceLoader 来获得实现方的实例的。
Java SPI 需要四个组件。
1) Service Provider Interface, 一个接口或者抽象类,是调用方和实现方约定的接口内容
2) Service Provider, 实现方对上述接口内容的具体实现
3) SPI Configuration File, 必须位于 Service Provider 提供的包的 META-INF/services/ 路径下,文件名是 SPI 接口或抽象类的全路径名(例如 java.sql.Driver),文件中的每一行是 Service Provider 对 SPI 接口或抽象类的具体实现类的全路径名(例如 com.mysql.jdbc.Driver)
4) ServiceLoader, Java SPI 的枢纽类,由 jdk 提供,可以帮助调用方创建 SPI 实现类的实例,供调用方使用。 除此之外,还需要有一个地方调用 ServiceLoader 的 load 方法去初始化服务,例如 JDBC 的 DriverManager。即下文桥接模式中的 Implementor。 服务使用方使用到服务时,也才会真正意义上去发现服务,以完成服务的初始化,形成了服务的动态加载。
除了 jdk 提供的 ServiceLoader,也可以自行实现相应的工具类,例如 SpringFactoriesLoader 就是 Spring Boot 内部提供的服务加载方式,只要引入了 spring-boot-autoconfigure 应用,并在模块 META-INF/spring.factories 文件中加入相应的 Bean 工厂类,Spring 就会自动创建相应的 Bean 服务。
JDBC 与 Bridge 设计模式
JDBC 的服务端采用 SPI 的实现方式,而其对客户端提供服务时采用了桥接模式。 参考:结构型-桥接模式详解
结合桥接模式的类图,Abstraction 是对 Client 提供的 API 抽象接口。
一般的,我们可以通过实现 Abstraction 接口来提供功能,这种情况下如果我们要更改 Abstraction 接口(例如新增一个功能),相应的实现类都要更改。
桥接模式下,采用关联关系取代继承关系,即在对 Client 提供 API 的中组合一个实际上提供接口功能的实例(Implementor),在接口的 operation 方法中,调用该实例的相应方法,为 Client 提供功能。Implementor 接口作为对外 API 接口和其功能实现类之间的桥。
JDBC 是桥接模式的一个典型应用,其核心类图如下。
从类图上可以看出来,对 Client 提供功能的接口实例中需要组合 Implementor 的实例,那么对于 JDBC 来说这个实例是什么时候产生的呢?在 jdk 1.5 之前,开发者需要手动调用 Class.forName("driverfullclassname");
才能使用相应的数据库驱动,数据库驱动供应商会在驱动类的静态代码块产生 Implementor 的实例,并赋值到 Abstraction 中。
static
{
try
{
//DriverManager 是 Abstraction,new Driver() 产生当前 Implementor 的实例
DriverManager.registerDriver(new Driver());
}
catch(SQLException E)
{
throw new RuntimeException("Can't register driver!");
}
}
从 jdk 1.6 开始,DriverManager 中新增了使用 ServiceLoader 加载数据库驱动的静态代码块,就不需要开发者去加载相应的驱动了。
static {
//利用 SPI 机制加载所有 classpath 中按照指定规则提供了的 driver
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
桥接模式降低类之间的耦合,遵循开闭原则和单一职责原则。 总的来说,在 Java SPI 的四个组件中,java.sql.Driver 是Service Provider Interface;各个厂商提供的 Driver 实现是 Service Provider;各个厂商提供的 Driver 实现 package 中包含 SPI Configuration File;ServiceLoader 由 jdk 提供。
Java SPI 与类加载机制
我们知道,java 类通过双亲委派机制加载。对于 JDBC 来说,java.sql.DriverManager 位于 rt.jar 中,该类是由 BootstrapClassloader 类加载器加载的,而我们在 DriverManager 中去加载 com.mysql.jdbc.Driver 类,默认的双亲委派机制下,将会使用加载 DriverManager 的类加载器即 BootstrapClassloader 去加载 com.mysql.jdbc.Driver,但 BootstrapClassloader 当然不会去加载 jdk library 以外的包,就会出现错误。为了能顺利加载相应的类,ServiceLoader 中使用 Thread.currentThread().getContextClassLoader()
获得当前线程的类加载器,一般都是 AppClassLoader,然后使用该类加载器加载驱动。