SPI机制及JDBC加载驱动再理解

1、什么是SPI

SPI全称为 Service Provider Interface ,直译为服务提供者接口,源自服务提供者框架(Service Provider Framework),是一种将服务接口与服务实现分离以达到解耦、提升了程序可扩展性的机制。

在Java中一个非常典型实例是:JDBC

sun公司为了实现Java连接各大数据库,编写了JDBC接口,由各家数据库编写实现。

回忆一下数据连接六步:

  1. 加载驱动
  2. 获取连接
  3. 获取SQL执行器
  4. 执行SQL
  5. 获取结果集
  6. 关闭连接

以MySQL为例,对应的代码如下

public class TestJDBC {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {

        // 这一步可以省略!
        Class.forName("com.mysql.cj.jdbc.Driver");
        
        // 获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/testdb","root","123");
        
        // 获取SQL执行器
        Statement statement = connection.createStatement();

        // 执行SQL,获取查询结果集
        ResultSet resultSet = statement.executeQuery("SELECT * FROM users");
        
        // 关闭连接
        connection.close();
    }
}

为什么说类加载可以省略?

但是我想在知道原因之前你应该还有一个疑问就是,为什么类加载这一步可以加载驱动?

其实严格来说应该叫注册驱动,在com.mysql.cj.jdbc包下找到Driver这个类,你会发现一个静态代码块。

static {
    try {
        DriverManager.registerDriver(new Driver());
    } catch (SQLException var1) {
        throw new RuntimeException("Can't register driver!");
    }
}

我们都知道,静态代码块在类加载的时候执行,这一步就会帮我们将驱动注册好,也就是将JDBC接口和MySQL的实现绑定,我们只需要调用接口就可以了。

如何注册的?

进入DriverManager这个类,里面有一段静态代码块:

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

上面的意思是JDBC的DriverManage已经初始化完成!那肯定和loadInitialDrivers()有关,点开它,我发现这么一段代码

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();

try{
    while(driversIterator.hasNext()) {
        driversIterator.next();
    }
} catch(Throwable t) {
    // Do nothing
}

PS: ServiceLoader是JDK6开始提供的一个实现SPI的类,说白了,就是给它一个接口,它来帮你找实现类,如何找在下面再解释!

现在可以说,为什么Class.forName("com.mysql.cj.jdbc.Driver");可以省略了。

在MySQL驱动 5.1.6 以后可以无需 Class.forName(“com.mysql.cj.jdbc.Driver”);使用了SPI机制,会自动去发现服务的实现。

image-20211122200055584

在Java中,SPI是去在Classpath下的META-INF/services发现接口的实现类的,以接口的全类名作为文件名,文件中每一行是这个接口实现类的全类名。

例如上图JDBC中的Driver全类名作为文件名,其中内容为

com.mysql.cj.jdbc.Driver

这样在程序启动的时候就会自动加载com.mysql.cj.jdbc.Driver这个类!

2、Java中是如何实现的SPI

实现的机制在上面已经提到了,我们这里就编写一个例子验证!

2.1 模拟JDBC

创建一个普通java项目作为接口包(你也可以用maven)

package cn.butcher;

/**
 * 驱动接口,其中有一个获取连接的方法
 */
public interface MyDriver {
    void getConnection();
}

image-20211122214626527

是的整个项目就只有这一个接口类~

然后打成jar包,打包方法可百度哈,我这里jar的名字为:bt-driver.jar

2.2 模拟mysql驱动

同样创建一个普通的java项目,导入bt-driver.jar,创建自己的实现类

package com.butcher;

import cn.butcher.MyDriver;

public class MyDriverImpl implements MyDriver {
    @Override
    public void getConnection() {
        System.out.println("hello, this is MyDriverImpl!");
    }
}

为了区分,我使用了不同的包名,以说明这也接口不是同一个项目。

然后在classpath下创建META-INF/services文件夹,创建文件名为cn.butcher.MyDriver的文件,在其中加入实现类的全限定名

com.butcher.MyDriverImpl

最终项目结构:

image-20211122215456819

ok,打成jar包 :butcher-driver.jar

2.3 模拟在自己项目中使用

创建一个项目,先引入bt-driver.jar这个依赖,然后创建一个类测试

import cn.butcher.MyDriver;

import java.util.Iterator;
import java.util.ServiceLoader;

public class Test {
    public static void main(String[] args) {

        // 使用ServiceLoader加载META-INF/services下的配置的服务
        ServiceLoader<MyDriver> loader = ServiceLoader.load(MyDriver.class);

        // 获取迭代器,也就是可以有多个接口的实现,这里我只拿一个,拿多个用while即可
        Iterator<MyDriver> iterator = loader.iterator();

        if (iterator.hasNext()) {
            MyDriver driver = iterator.next();
            driver.getConnection();
        } else {
            System.err.println("no such MyDriver implement !");
        }
    }
}

运行结果:

no such MyDriver implement !

没有找到实现类?当然找不到,因为我们实现类都没有引入。这时候引入butcher-driver.jar

代码不变,再次运行:

hello, this is MyDriverImpl!

有两个坑说明一下

  1. 如果使用idea进行打包,那么重新打包的时候需要将你之前打包的删掉,不然有可能出现一些诡异的现象。
  2. 如果使用idea创建cn.butcher.MyDriver这个文件的时候选文件类型,选Text类型就可以了,这并不会将这个文件类型修改为.txt文件,而是告诉idea,虽然你不知道这个文件的类型,但请你以文本的形式给我打开就行了!

3、总结

OOP七大原则中有一条,面向接口编程,JDBC就是一个很好例子,如果我们把JDBC写死了,那切换数据的时候就会非常麻烦,类似于slf4j也是一样的初心。

Java的SPI类似于IOC的功能,将装配的控制权移到了程序之外,实现在模块装配的时候不用在程序中动态指明。所以SPI的核心思想就是解耦,这在模块化设计中尤其重要。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值