一、先搞懂:什么是双亲委派模型?
双亲委派模型是 JVM 类加载的核心机制。简单来说,它的原则是“孩子找爸爸,爸爸找爷爷”,旨在确保类的安全性与全局唯一性。你或许会问,为什么需要这么复杂的查找链条?核心目的是为了保护 Java 核心基础类库(如 java.lang.String)不会被用户自定义的类随意替换,从而保障系统稳定。
核心执行流程(一步都不能错)
- 先查缓存:当一个类加载器需要加载某个类时,它首先会查询自己的已加载类缓存。如果找到,则直接返回。这步缓存命中是提升效率的关键,但常被初学者忽略。
- 向上委派:如果自己的缓存中没有,它并不会立即尝试加载,而是将加载请求委派给自己的父类加载器(即“爸爸”)去处理。
- 继续向上委派:如果父类加载器在自己的缓存中也没有找到,它会继续将请求向上委派给它的父类加载器(即“爷爷”)。这个过程会一直持续到启动类加载器(Bootstrap ClassLoader)。
- 向下尝试加载:如果启动类加载器在其负责的路径(如
JAVA_HOME/jre/lib)中也找不到目标类,请求就会开始“下降”。它会返回到扩展类加载器(Extension ClassLoader),由扩展类加载器尝试在其路径(如 JAVA_HOME/jre/lib/ext)中加载。
- 最后尝试加载:如果扩展类加载器加载失败,请求最终会回到最初发起请求的应用程序类加载器(Application ClassLoader)。此时,应用程序类加载器会在其类路径(ClassPath)中尝试查找并加载该类。如果成功,则缓存并返回;如果失败,则抛出
ClassNotFoundException。
这里引出一个关键问题:启动类加载器能加载应用类加载器路径下的类吗? 答案是不能。这就导致了一个经典的场景:如果一个接口的定义由启动类加载器加载,而其实现类位于应用程序类加载器的路径下,那么按照标准的双亲委派模型,父加载器(启动类加载器)是无法“看到”或加载子加载器(应用类加载器)路径下的实现类的。这个矛盾,正是 SPI(Service Provider Interface)机制要解决的核心问题。
图1:双亲委派模型的核心“向上委派,向下加载”流程示意图
二、SPI:打破双亲委派的“解耦神器”
学习 SPI 时会发现一个有趣的现象:它巧妙地绕开了双亲委派的严格层级限制。它是如何做到的呢?我们先明确 SPI 的定义,再结合具体实例分析其运作逻辑。
1. 什么是SPI?
SPI(Service Provider Interface)是 JDK 内置的一套服务发现机制。它的核心思想是“解耦”——将接口的定义与具体实现的装配权,从程序内部转移到了程序外部(通常是 JAR 包的配置文件中)。这在框架开发中极为常见。例如,java.sql.Driver 是一个标准接口,而 MySQL、PostgreSQL 等数据库厂商各自提供了实现。SPI 机制让 JDBC 能在运行时动态发现并加载这些厂商驱动。
2. 源码视角:SPI如何工作?(以数据库驱动为例)
JDK 定义了 java.sql.Driver 接口,各数据库厂商负责实现它。以 MySQL 的 Driver 类为例,查看源码会发现,它在静态代码块中调用了 DriverManager.registerDriver() 方法来注册自身。

图2:MySQL JDBC Driver类中的静态注册代码
那么,这个静态代码块是如何被触发执行的呢?关键在于 SPI 的核心类 ServiceLoader。整个流程可以分为两个关键阶段:
阶段一:DriverManager的驱动初始化
当应用程序首次调用 DriverManager.getConnection() 尝试建立数据库连接时,会触发驱动初始化。这个过程中,DriverManager 会调用 ServiceLoader.load(Driver.class) 方法。

图3:DriverManager.getConnection方法触发驱动初始化
这个方法会获取当前线程的上下文类加载器(默认是应用程序类加载器),然后用它去加载服务提供者。接着,ServiceLoader 会扫描所有 META-INF/services/ 目录下的以接口全限定名命名的配置文件,读取其中声明的实现类全路径名。

图4:ServiceLoader利用线程上下文类加载器加载实现类

图5:SPI通过扫描配置文件发现服务实现
阶段二:驱动注入与建立连接
扫描到实现类路径后,ServiceLoader 会利用应用程序类加载器去实例化这个类。一旦类被加载和初始化,其静态代码块就会执行,从而将驱动实例注册到 DriverManager 的维护列表中。之后,当真正调用 getConnection 建立连接时,DriverManager 就会遍历已注册的驱动列表,尝试连接。

图6:DriverManager遍历已注册的DriverInfo列表建立连接
至此,我们就清晰地看到了 SPI 如何解决前面的矛盾:由启动类加载器加载的 DriverManager 类,通过 SPI 机制,巧妙地让应用程序类加载器去加载并实例化位于 ClassPath 下的数据库驱动实现类。这实际上是通过线程上下文类加载器(Thread Context ClassLoader) 临时打破了双亲委派的层级限制,实现了父加载器请求子加载器完成类加载的行为。
|