SPI 技术:JDBC加载不同数据库厂商的驱动

    技术2022-07-11  94

    SPI 技术

    SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。

    Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。

    实现方式:

    当服务的提供者提供了一种接口的实现之后,需要在 Classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,此文件记录了该 jar 包提供的服务接口的具体实现类。当某个应用引入了该 jar 包且需要使用该服务时,JDK SPI 机制就可以通过查找这个 jar 包的 META-INF/services/ 中的配置文件来获得具体的实现类名,进行实现类的加载和实例化,最终使用该实现类完成业务功能。

    样例

    抽象接口类:

    public interface Log { public void log(String log); }

    两个实现类:

    public class Log4j implements Log { @Override public void log(String log) { System.out.println("log4j:" + log); } } public class Logback implements Log { @Override public void log(String log) { System.out.println("logback " + log); } }

    测试类:

    public class SpiTestMain { public static void main(String[] args) { ServiceLoader<Log> serviceLoader = ServiceLoader.load(Log.class); Iterator<Log> iterator = serviceLoader.iterator(); while (iterator.hasNext()) { Log log = iterator.next(); log.log("JDK SPI"); } } }

    在项目的 resources/META-INF/services 目录下添加的文件以及内容

    运行结果:

    源码分析

     

    JDBC加载不同数据库厂商的驱动

    以我使用的mysql-connector-java-8.0.20.jar为例,在jar 包中的 META-INF/services 目录下,有一个 java.sql.Driver 文件中只有一行内容

    com.mysql.cj.jdbc.Driver

    如果我们手动加载JDBC通常会写这样的代码:

    String url = "jdbc:xxx://xxx:xxx/xxx"; Connection conn = DriverManager.getConnection(url, username, pwd);

    DriverManager 是 JDK 提供的数据库驱动管理器,它的静态代码块:

    static { loadInitialDrivers(); println("JDBC DriverManager initialized"); }

    在调用 getConnection() 方法的时候,DriverManager 类会被 Java 虚拟机加载、解析并触发 static 代码块的执行

     

    private static void loadInitialDrivers() { String drivers; try { drivers = AccessController.doPrivileged(new PrivilegedAction<String>() { public String run() { return System.getProperty("jdbc.drivers"); } }); } catch (Exception ex) { drivers = null; } AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); Iterator<Driver> driversIterator = loadedDrivers.iterator(); try{ while(driversIterator.hasNext()) { //该地方虽然只是调用了next,但是却是会获取到Driver的实现类,触发实现类的初始化 driversIterator.next(); } } catch(Throwable t) { // Do nothing } return null; } }); println("DriverManager.initialize: jdbc.drivers = " + drivers); if (drivers == null || drivers.equals("")) { return; } String[] driversList = drivers.split(":"); println("number of Drivers:" + driversList.length); for (String aDriver : driversList) { try { println("DriverManager.Initialize: loading " + aDriver); Class.forName(aDriver, true, ClassLoader.getSystemClassLoader()); } catch (Exception ex) { println("DriverManager.Initialize: load failed: " + ex); } } }

    注意:driversIterator.next();该代码虽然只是调用了next,但是却是会获取到Driver的实现类,触发实现类的初始化,以配置文件配置的com.mysql.cj.jdbc.Driver为例

    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!"); } } }

    可见,静态代码块向DriverManager注册了自己。

     

    Processed: 0.009, SQL: 9