【从加载数据库驱动包,理解java SPI】

SPI(Service Provider Interface)

从1.6引入,基于ClassLoader 来加载并发现服务的机制

对于msyql驱动

引入依赖

xml 复制代码
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.27</version>
    </dependency>

在mysql:mysql-connector-java:8.0.27.jar中,可以找到META-INF/services/java.sql.Driver文件:

复制代码
com.mysql.cj.jdbc.Driver

找到com.mysql.cj.jdbc.Driver源码:

java 复制代码
package com.mysql.cj.jdbc;

import java.sql.DriverManager;
import java.sql.SQLException;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}
  1. 它实现了java.sql.Driver
  2. 静态代码块中,向DriverManager注册了自己。

对于oracle驱动

引入依赖

xml 复制代码
<dependency>
    <groupId>com.hynnet</groupId>
    <artifactId>oracle-driver-ojdbc6</artifactId>
    <version>12.1.0.1</version>
</dependency>

在com.hynnet:oracle-driver-ojdbc6-12.1.0.1.jar中,可以找到META-INF/services/java.sql.Driver文件:

复制代码
oracle.jdbc.OracleDriver

找到oracle.jdbc.OracleDriver源码:

java 复制代码
public class OracleDriver implements Driver {
    //
    static {
        try {
            if (defaultDriver == null) {
                defaultDriver = new oracle.jdbc.OracleDriver();
                DriverManager.registerDriver(defaultDriver);
            }
        } catch  // ... 
    }
}
  1. 它实现了java.sql.Driver
  2. 静态代码块中,向DriverManager注册了自己。

从DriverManager跟踪源码

对于 DriverManager:
java 复制代码
public class DriverManager {
    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
    
    
    private static void loadInitialDrivers() {
        
         AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                
                    while(driversIterator.hasNext()) { // 断点进到这里
                        driversIterator.next();
                    }
            }
        });
        
        // 最后还是根据拿到了从META-INF/services/java.sql.Driver里的Driver,如com.mysql.cj.jdbc.Driver
        Class.forName(aDriver, true, ClassLoader.getSystemClassLoader());
    }
}
在ServiceLoader里:
java 复制代码
public final class ServiceLoader<S> implements Iterable<S> {

    private static final String PREFIX = "META-INF/services/";
    
     public boolean hasNext() { // 进入driversIterator.hasNext() 后追到这里
        if (acc == null) {
            return hasNextService();
        }
    }    
    
        
    private boolean hasNextService() {
        String fullName = PREFIX + service.getName();
        configs = ClassLoader.getSystemResources(fullName); // 断点没有进
        configs = loader.getResources(fullName); // 断点进到这里
    }
}

是在某个时机,触发DriverManager静态代码块,且只会执行这一次,紧接着一系列调用:

--> loadInitialDrivers()

--> ServiceLoader.load()、driversIterator.hasNext()

--> hasNextService()

--> loader.getResources(fullName)

使用classLoader.getResources() 加载所有包下"META-INF/services/java.sql.Driver"文件

获取连接测试

当使用Class.forName时
java 复制代码
Class.forName("com.mysql.cj.jdbc.Driver"); // 断点由此进入DriverManager静态代码块断点
Connection conn = DriverManager.getConnection("***", "root", "123456");

Class.forName() 中有一个本地方法

java 复制代码
 private static native Class<?> forName0(String name, boolean initialize, ClassLoader loader, Class<?> caller) throws ClassNotFoundException;

经过这个本地方法后,断点进入DriverManager静态代码块断点,本地方法里做了啥? 如何会触发DriverManager的类加载,不知道。。

但DriverManager静态代码确实只会执行一次,即便再次使用 Class.forName("com.mysql.cj.jdbc.Driver")时, 不会进入DriverManager静态代码块断点了

当不使用Class.forName,而直接DriverManager.getConnection时
java 复制代码
Connection conn = DriverManager.getConnection("***", "root", "123456"); // 断点由此进入DriverManager静态代码块断点

getConnection() 是静态方法,会触发执行DriverManager静态代码块,接着也是一系列的调用,加载所有包下"META-INF/services/java.sql.Driver"文件。

总结

通过 DriverManager、ServiceLoader,在第一次获取连接前,总会去执行一次loader.getResources(fullName),加载所有包下"META-INF/services/java.sql.Driver"文件中指定的类。

过程都由DriverManager、ServiceLoader设计好,获取驱动包名的目录"META-INF/services"也有ServiceLoader定义为常量。

所以实现一个驱动,需要按照约定,在META-INF/services/java.sql.Driver中准备好驱动所在的全

限定名。便会有ClassLoader来发现和加载相应驱动。

相关推荐
AntsData26 分钟前
用 12K+ 条亚马逊评论信号,定位机械键盘下一版该改什么
java·大数据·计算机外设
code2cat33 分钟前
【随笔】连续批处理:长短请求怎样在迭代之间补位
java·人工智能·后端·大语言模型·vllm
最强小杰36 分钟前
DeepSeek V4 Flash + Codex CLI 接入教程:原生 Responses API 适配,从配置到跑通收藏这篇就够了(2026)
java·前端·python·ai
Flynn Liang1 小时前
Spring AI 2.0 深度解析:ChatClient、Advisor 链与 MCP——Java 工程师的 AI 落地最优解
java·人工智能·spring·大模型·架构设计·spring ai
灿飞1 小时前
Kotlin与Java虚拟机六:基础与类型系统:一切皆对象,但对象有代价
java·kotlin
计算机毕设定制辅导-无忧学长1 小时前
《基于微信小程序健康习惯激励系统设计与实现》
java·spring boot·mysql·微信小程序·uniapp·健康习惯激励系统
雪芽蓝域zzs2 小时前
第 1 章:IDEA 创建 SpringBoot3.5.16 Maven 项目
java·maven·intellij-idea
golang学习记2 小时前
IDEA 开始换芯,发布全新AI版终端
java·人工智能·intellij-idea
vipxieliang2 小时前
ValidX 正则表达式预编译优化:Pattern 复用策略
java·spring boot·后端
Wang's Blog3 小时前
Java 项目实战:交友项目 - FastDFS 工作原理
java·大数据·交友