面试复习系列-SPI与JDBC加载

511 阅读3分钟

参考

SPI的基本概念

SPI和API是不同但是相似的东西,它们最根本的不同就是:接口是使用方来指定的(SPI),还是接口是由实现方来指定的(API)。

API示例

例如我们引入了netty包,netty包本身提供了eventLoop接口,并且提供了具体的实现,像这种接口和实现均在实现方中提供的情况,就是API。我们引入软件包就可以开始使用API进行编程。

SPI示例

例如我们使用JDBC的时候,JDBC提供了一个Connection接口,但是没有提供任何实现。这个实现是如何来的呢,就是通过我们引入的mysql、oracle等的第三方客户端jar包来的。这些第三方jar包并不包含接口的具体内容,而是实现了使用方所声明的接口,这种情况就是SPI。

SPI和API的区别

  • API:实现方对使用方说【我提供了XXX功能,要使用该功能请使用IXXX接口进行编程】
  • SPI:使用方说【我提供了IXXX接口,你们(实现方)如果想给我提供功能的话,请按照该IXXX接口进行实现】

API是怎么找到具体实现的

  • 通常,实现方会提供一个工厂方法,创建出API接口的实现类的具体实例给我们使用,这种情况下实现类是工厂方法确定的。
  • 有时,实现方会提供几个不同的实现,然后我们可以自己选择,通过简单的new出某一个具体实现类的方式。这种情况下实现类是由使用者自己手动指定的。

SPI是怎么找到具体实现的

SPI使用ServiceLoader,可以加载jar包中的文件 META-INF/services/接口名称 。然后加载该文件里定义的接口实现类(可以定义多个实现类),最后就加载了很多个具体的实现。

JDBC的基本过程

  • 首先我们肯定要调用DriverManager.getConnection(xxx)
  • 然后因为我们有上面的调用,因此会加载DriverManager类,从而执行类初始化
    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
    
    private static void loadInitialDrivers() {
        xxx...
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                // 关键步骤
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });
    }
  • 比较关键的就是其中使用SPI去加载了 Driver.class 的实现类,然后进行了实例化
  • Driver的实现类中,他们将自己注册到DriverManager中
  • 最后getConnection的时候,就会在DriverManager中注册的驱动里,一个一个的执行获取连接操作。数据库驱动会根据URL来获取连接,发现不是自己需要处理的数据库,就返回null,进入下一个驱动。一直到某一个驱动可以处理某一种URL,再去实际的连接该数据库
  • 也就是说不需要类似于Class.forName("com.mysql.jdbc.Driver")的代码去手动的加载驱动类了。这种加载已经被SPI机制所取代了。