Java 的 SPI 机制

SPI 全称为 Service Provider Interface,是 JDK 内置的一种服务提供发现机制。SPI 是一种动态替换发现的机制,比如有个接口,想运行时动态的给它添加实现,你只需要添加一个实现。我们经常遇到的就是 java.sql.Driver 接口,其他不同厂商可以针对同一接口做出不同的实现,mysql 和 postgresql 都有不同的实现提供给用户,而 Java 的 SPI 机制可以为某个接口寻找服务实现。

SPI 简介

Java 6 提供了一个 java.util.ServiceLoader 类,用于实现 Service Provider Interface(SPI)。

SPI 是什么东西呢?以 JDBC 为例子,JDBC 分为两部分,一个是 JDBC API,由 JDK 提供;另一个是 JDBC 驱动实现,由数据库厂商提供。JDBC API 提供了一个数据库驱动接口,java.sql.Driver,Driver 接口的具体实现类则位于不同的数据库厂商 JDBC 驱动中,如 MySQL 的 JDBC 驱动实现类为:com.mysql.jdbc.Driver。

当我们使用 JDBC 访问数据库时,通常的步骤为:

  1. 调用 Class.forName("com.mysql.jdbc.Driver"),注册对应的 JDBC 驱动;
  2. 调用 DriverManager.getConnection(url, user, pass),获取数据库连接。

注意,因为 Class.forName() 中指定了对应的 Driver 实现类的全限定类名,即驱动实现类的类名硬编码在我们的应用程序中,如果后期我们需要更换 Driver 实现类,比如换为 Oracle 的驱动,那么就需要修改程序的代码,这不符合开闭原则。为了解耦,JDK1.6 提供了 SPI 机制,JDBC 4.0 规范开始支持 SPI 机制,这样就不需要在程序中编写 Class.forName() 代码了。

那么 JDBC 4.0 是如何知道要使用什么 java.sql.Driver 实现类呢?很简单,JDK 规定,如果需要使用 SPI 机制,只需要在 ClassPath 路径中的 META-INF/services/java.sql.Driver 文件中放入具体的 java.sql.Driver 实现类的全限定类名就可以了。注意前缀为 META-INF/services/,而 java.sql.Driver 是文件名(UTF-8 编码的文本文件),文件名是有讲究的,即对应的接口名称,而文件内容是按行分割的,每行都是一个对于接口的实现类的全限定类名,比如 com.mysql.jdbc.Driver

JDBC 4.0 在初始化时,会寻找 classpath 中的 META-INF/services/java.sql.Driver 文件(可以有多个,会自动合并在一起),然后实例化这些驱动类,然后我们在调用 DriverManager.getConnection() 方法时,JDBC 4.0 会自动选择合适的驱动实现类(具体如何选择请自行查看 JDBC 4.0 规范以及源码)。

SPI 例子

接口:com.zfl9.service.Service:

实现类:com.zfl9.service.impl.HelloService:

实现类:com.zfl9.service.impl.WorldService:

SPI 文件:META-INF/services/com.zfl9.service.Service:

Main 类:com.zfl9.service.ServiceMain:

运行结果: