Java SPI 通过 ServiceLoader.load(Driver.class) 自动加载 META-INF/services/java.sql.Driver 中声明的驱动类,DriverManager 调用其 acceptsURL 判断是否匹配 JDBC URL;MySQL 5.x 与 8.x 驱动类名和 URL 支持不同,导致换版本后连接失败;ServiceLoader 默认吞异常,需手动遍历捕获 ServiceConfigurationError 定位问题驱动。

Java SPI 是怎么让 DriverManager 自动发现数据库驱动的
不是靠你手动 Class.forName(),也不是靠写死 new MySQLDriver(),而是 JVM 启动时扫描 META-INF/services/java.sql.Driver 文件,把里面写的全限定类名全部加载并实例化。只要驱动 JAR 包里有这个文件、且内容正确(比如 com.mysql.cj.jdbc.Driver),DriverManager 就能在调用 getConnection() 前自动注册它。
关键点在于:DriverManager 内部调用了 ServiceLoader.load(Driver.class),而 ServiceLoader 正是 Java SPI 的标准实现——它只认 META-INF/services/ 下对应接口的文本文件,不看 classpath 顺序,也不依赖 Spring 或其他框架。
- 必须是
java.sql.Driver接口,不能是子类或自定义接口 - 文件路径必须严格为
META-INF/services/java.sql.Driver(大小写敏感) - 每行一个驱动类全限定名,末尾不能有多余空格或 BOM
- JDK 6+ 默认启用,但如果你用模块系统(
module-info.java),得显式uses java.sql.Driver;
为什么换 MySQL 驱动时改了依赖却连不上 jdbc:mysql://
现象:Maven 换成 mysql-connector-java:8.0.33,代码没动,但 SQLException: No suitable driver found。根本原因不是驱动没加载,而是 URL 协议和驱动类不匹配。
MySQL 5.x 和 8.x 的驱动类名、URL 支持范围、默认行为都变了。JDBC URL 是交给具体驱动解析的,DriverManager 只负责转发;如果当前已加载的驱动里没有一个返回 true 给 acceptsURL("jdbc:mysql://..."),就会报这个错。
立即学习“Java免费学习笔记(深入)”;
- MySQL 5.1.x 用
com.mysql.jdbc.Driver,只支持jdbc:mysql:// - MySQL 8.0+ 用
com.mysql.cj.jdbc.Driver,支持jdbc:mysql://和jdbc:mysql:aurora://等 - Spring Boot 2.4+ 默认排除了旧驱动,但如果你手工引入两个版本,
ServiceLoader会全加载,然后按顺序尝试acceptsURL,第一个返回 true 的才真正干活 - 检查方法:调试时打个断点在
DriverManager.getConnection,看registeredDrivers列表里有哪些类,再逐个调driver.acceptsURL(url)
ServiceLoader 加载失败但没报错,怎么定位是哪个驱动坏了
ServiceLoader 遇到类加载失败、构造器异常、静态块抛错,**默认吞掉异常,只跳过该实现类**。这意味着你可能以为驱动“存在”,其实它根本没初始化成功,后续 acceptsURL 返回 false,连接就静默失败。
最直接的办法是手动触发加载并捕获异常:
ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
for (Driver driver : loader) {
System.out.println("Loaded: " + driver.getClass().getName());
}
这样一旦某个驱动构造失败,就会抛出 ServiceConfigurationError,堆栈里能清楚看到是哪个类、哪行代码崩的。
- 常见诱因:驱动依赖了不存在的类(如旧版 HikariCP 引入了 JDK 9+ 的 API)、
static块里读配置文件失败、日志框架冲突导致Logger.getLogger()抛 NPE - 不要依赖 IDE 的 “External Libraries” 视图来判断驱动是否在 classpath —— 它只显示 JAR,不反映
META-INF/services是否合法 - 用
jar -tf mysql-connector-java-8.0.33.jar | grep services快速确认文件是否存在
多数据源场景下,SPI 加载的驱动会不会互相干扰
不会。SPI 本身只是加载类,不管理状态;真正决定用哪个驱动的是 DriverManager 拿着 URL 去问每个已注册的 Driver:“你能处理这个 URL 吗?”——这是纯逻辑判断,无共享状态。
但现实中的干扰来自两处:一是多个驱动共用同一套全局配置(如 TimeZone 参数被某一个驱动改了 JVM 时区),二是连接池(如 HikariCP)缓存了 Driver 实例后,后续不再走 DriverManager 的发现流程。
- HikariCP 会在第一次
getConnection时调用DriverManager.getDriver(url),之后复用该实例;所以你换驱动 JAR 后必须重启应用,不能热替换 - PostgreSQL 驱动(
org.postgresql.Driver)和 MySQL 驱动互不感知,只要 URL 前缀不同(jdbc:postgresql://vsjdbc:mysql://),就不会抢活 - 如果自己封装了数据源工厂,别在静态块里预加载所有驱动——按需调用
ServiceLoader.load()更可控
真正麻烦的从来不是 SPI 机制本身,而是驱动 JAR 里那些藏在 static 块、Driver 构造器、甚至 java.sql.Driver 子接口实现里的副作用。它们不会报错,但会让连接行为变得不可预测。











