博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

从jdbc4.0 不再需要再写Class.forName("")到SPI技术

Posted on 2020-11-28 19:07  海绵谷  阅读(385)  评论(0编辑  收藏  举报

一开始学习JDBC的时候到现在一直认为连接数据库之前首先需要

Class.forName("com.mysql.jdbc.Driver");

直到某一天至少是在写这篇博客之前。为什么注释掉了还是可以连接数据库?这个问题先等一等看。
先回忆下实例化对象的几种方式

        //1.直接new
        Hello hello = new Hello();
        hello.say();
        //2.Class.forName.newInstance
        Hello hello2 = (Hello) Class.forName("com.mySpi.Hello").newInstance();
        hello2.say();
        //3.需实现Serializable, 运用反序列化手段,调用java.io.ObjectInputStream对象的 readObject()方法。

继续我们问题,既然不需要显示加载,那肯定是有其他的类替我们加载了,首先看DriverManager,因为除了main方法所在的类,最先调的只有他了,再找下该类下面有没有静态代码块,因为它是最先被jvm加载的,然后我们会发现

/**
     * Load the initial JDBC drivers by checking the System property
     * jdbc.properties and then use the {@code ServiceLoader} mechanism
     */
    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

然后执行loadInitialDrivers();代码如下

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;
        }
        // If the driver is packaged as a Service Provider, load it.  如果驱动是作为服务提供,就去加载
        // Get all the drivers through the classloader 通过classloader 获取所有的驱动
        // exposed as a java.sql.Driver.class service. Driver接口的有实现类作为服务
        // ServiceLoader.load() replaces the sun.misc.Providers() 加载实现类

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

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


在loadInitialDrivers()方法中,我们发现通过ServiceLoader机制加载java.sql.Driver接口的实现类,然后对所有实现类进行遍历,来完成了驱动类的加载。
那么问题来了,jdk是如何知道java.sql.Driver有哪些实现类的呢?
主角登场:

SPI,官方话是JDK内置的一种服务提供发现机制。比如有一个接口,想在运行时动态地给它添加实现,只需要按照SPI的规矩来办,那么SPI机制在程序运行时就会发现该实现类;规则如下:
在classpath下META-INF/services 新建一以接口名(如java.sql.Driver)为名称的文件,文件内容是接口实现类(如mysql驱动包下META-INF/services/java.sql.Driver里的com.mysql.jdbc.Driver),mysql-connect--的示例文件如下



所以不用显示Class.forName的原因是:核心ServiceLoader类提供了一个静态的load()方法,用于加载指定接口的所有实现类。调用该方法后,classpath下META-INF/services目录的java.sql.Driver文件中指定的所有实现类都会被加载。

现在我们知道spi机制的核心方法,可以写一个demo 实现下这种情况
首先新建SayHello接口,然后新建它的两个实现类Hello,Hi,代码如下

public interface SayHello {
    void say();
}
public class Hello implements SayHello{
    @Override
    public void say() {
        System.out.println("hello world--hello");
    }
}
public class Hi implements SayHello {
    @Override
    public void say() {
        System.out.println("hello world --hi");
    }
}

首先在resource文件下新建META-INF文件夹,然后META-INF下新建services文件夹,该文件夹下新建com.mySpi.SayHello文件,内容如下

下面我们写一个main 方法测试是否可以加载实现类,如果已经加载,就可以打印

 public static void main(String[] args) {
        ServiceLoader<SayHello> sayHellos = ServiceLoader.load(SayHello.class);
        Iterator<SayHello> driversIterator = sayHellos.iterator();
        while (driversIterator.hasNext()){
            SayHello hello=driversIterator.next();
            hello.say();
        }
    }


如果com.mySpi.SayHello里内容,那么将无法打印hello-world,SPI简单入门就到这里了。