SPI服务发现机制
2、SPI的工作机制与约定
Java内置的SPI机制遵循一个非常简单的约定:
-
在
META-INF/services/目录下,创建一个以 SPI接口的全限定名 命名的文件。 -
文件内容是该接口的 具体实现类的全限定名,每行一个。
-
程序通过
java.util.ServiceLoader类来动态加载和实例化这些实现。
3、一个经典的例子:JDBC
这是理解SPI为何要打破双亲委派的最佳例子。
步骤分解:
-
定义SPI接口
-
Java核心库(在
rt.jar中)定义了java.sql.Driver接口。
-
-
提供实现
-
MySQL厂商提供了
mysql-connector-java.jar,其中包含了com.mysql.cj.jdbc.Driver类,它实现了java.sql.Driver接口。 -
在这个JAR包的
META-INF/services/目录下,会有一个名为java.sql.Driver的文件。 -
文件内容只有一行:
com.mysql.cj.jdbc.Driver。
-
-
服务发现与加载
-
在你的Java程序中,你通常会写
Class.forName("com.mysql.cj.jdbc.Driver")(老方式)或者直接DriverManager.getConnection(...)(新方式)。 -
在
DriverManager的初始化阶段,它会使用ServiceLoader来加载所有在META-INF/services/java.sql.Driver文件中声明的驱动实现。
-
4、如何使用?
// 1. 通过ServiceLoader加载指定SPI接口的所有实现 ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class); // 2. 遍历并使用这些实现 for (Driver driver : loader) { System.out.println("Found driver: " + driver.getClass().getName()); // 可以根据需要调用driver的方法 // driver.connect(...); }
5、核心问题:类加载器的困境与打破双亲委派
现在我们来分析这个过程中的类加载器问题,这是SPI机制的精髓所在。
-
java.sql.Driver接口在哪里?-
它在
rt.jar中。 -
由
Bootstrap ClassLoader(启动类加载器)加载。
-
-
com.mysql.cj.jdbc.Driver实现类在哪里?-
它在应用程序的 ClassPath 下(比如
mysql-connector-java.jar)。 -
理应由
Application ClassLoader(应用类加载器)加载。
-
按照双亲委派模型:
-
当
ServiceLoader试图去加载com.mysql.cj.jdbc.Driver时,它会委派给它的父加载器Application ClassLoader。 -
Application ClassLoader能成功加载这个类吗?不能! 因为要加载com.mysql.cj.jdbc.Driver,首先需要先解析它的父类/接口,也就是java.sql.Driver。 -
Application ClassLoader会委派给它的父加载器Extension ClassLoader,后者再委派给Bootstrap ClassLoader。 -
Bootstrap ClassLoader负责加载核心库,它成功加载了java.sql.Driver接口。 -
现在问题来了:
Bootstrap ClassLoader只认识rt.jar中的核心类,它根本不认识、也无法加载位于ClassPath下的com.mysql.cj.jdbc.Driver类! -
于是,加载请求会层层向下传递,最终又回到了
Application ClassLoader。这次,Application ClassLoader发现自己找到了这个类,于是它成功加载了com.mysql.cj.jdbc.Driver。
这里的关键点在于:
一个由父加载器(Bootstrap)加载的接口,需要用到由子加载器(Application)加载的实现类。 这是标准的双亲委派模型无法直接支持的,因为父加载器无法“向下”去请求子加载器加载东西。
解决方案:线程上下文类加载器
为了解决这个问题,Java引入了 Thread Context ClassLoader(线程上下文类加载器)。
-
是什么? 它是绑定到当前线程的一个类加载器。
-
默认是什么? 如果没有特别设置,默认就是
Application ClassLoader。 -
谁打破了委派?
ServiceLoader在加载实现类时,并不使用它自己的类加载器(通常是Application ClassLoader或Extension ClassLoader),而是显式地使用当前线程的上下文类加载器(默认就是Application ClassLoader)来直接加载META-INF/services/中配置的实现类。
让一个核心库的类(由
Bootstrap 加载的 DriverManager),“借用”了一条位于子层的类加载器(Application ClassLoader),来加载本不属于自己管辖范围的类。这是一种父级委托子级加载的模式,完美地“打破”了标准的双亲委派模型。总结:


浙公网安备 33010602011771号