结合jdbc学习java spi机制

什么是SPI这里不做详细介绍,我是参考了简书上的 介绍https://www.jianshu.com/p/46b42f7f593c

其中有句话说的比较简洁到位:
Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。

  1. 首先看“基于接口编程”, java.sql.Driver接口,这个接口是定义在jdk包里的
  2. 再看“策略模式”,这个是在java.sql.DriverManager 类的java.sql.DriverManager#getConnection(java.lang.String, java.util.Properties, java.lang.Class) 方法里体现的
private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        /*
         * When callerCl is null, we should check the application's
         * (which is invoking this class indirectly)
         * classloader, so that the JDBC driver class outside rt.jar
         * can be loaded from here.
         */
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized(DriverManager.class) {
            // synchronize loading of the correct classloader.
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }

        if(url == null) {
            throw new SQLException("The url cannot be null", "08001");
        }

        println("DriverManager.getConnection(\"" + url + "\")");

        // Walk through the loaded registeredDrivers attempting to make a connection.
        // Remember the first exception that gets raised so we can reraise it.
        SQLException reason = null;
// 这里体现了策略模式,不同驱动会尝试获取链接,获取成功返回;获取失败则循环策略继续获取;
// 这里有个疑问registeredDrivers是怎么初始的呢?发现有个静态方法java.sql.DriverManager#registerDriver(java.sql.Driver, java.sql.DriverAction)可以对其进行初始化,而这个方法又是哪里在调用,先留个疑问,下一步给出答案
        for(DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        // Success!
                        println("getConnection returning " + aDriver.driver.getClass().getName());
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }

            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }

        }

        // if we got here nobody could connect.
        if (reason != null)    {
            println("getConnection failed: " + reason);
            throw reason;
        }

        println("getConnection: no suitable driver found for "+ url);
        throw new SQLException("No suitable driver found for "+ url, "08001");
    }

上面就是我们初学数据库连接时常有代码

Connection con = (Connection) DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl", "用户名", "密码");

的源码,今天才知道这里还用了策略模式

  1. 再看“配置文件”是怎么用的
    结合jdbc学习java spi机制_第1张图片
    这里配了spi的配置文件

但是这个配置文件怎么起作用的?
DriverManager 有个静态代码块在加载这个配置文件

 /**
     * 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方法,调用了ServiceLoader.load(Driver.class); 这里就加载了配置文件,并加载到jvm内存中

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
        // exposed as a java.sql.Driver.class service.
        // ServiceLoader.load() replaces the sun.misc.Providers()

        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

// 这里只是获取了ServiceLoader实例,还没有真正的加载java.sql.Driver的实现类
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();

                /* Load these drivers, so that they can be instantiated.
                 * It may be the case that the driver class may not be there
                 * i.e. there may be a packaged driver with the service class
                 * as implementation of java.sql.Driver but the actual class
                 * may be missing. In that case a java.util.ServiceConfigurationError
                 * will be thrown at runtime by the VM trying to locate
                 * and load the service.
                 *
                 * Adding a try catch block to catch those runtime errors
                 * if driver not available in classpath but it's
                 * packaged as service and that service is there in classpath.
                 */
                try{
                // hasNext方法会找到所有jar包下java.sql.Driver文件中的java.sql.Driver接口的实现类
                    while(driversIterator.hasNext()) {
                       // driversIterator是ServiceLoader中自定义的迭代器,调用next方法是将java.sql.Driver的实现类都会被加载到jvm内存  
                        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);
        // 这里加载的是系统属性中配置的,而不是SPI机制加载的类
        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);
            }
        }
    }

至此还留有一个疑问没解答,java.sql.DriverManager#registerDriver(java.sql.Driver, java.sql.DriverAction)方法是在哪里调用初始化registeredDrivers数组列表,(registeredDrivers列表是要在在getConnection方法中循环调用的)

看mysql 对java.sql.Driver接口的 实现

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    //
    // Register ourselves with the DriverManager
    // 静态块中调用了java.sql.DriverManager.registerDriver方法,而静态块在加载到jvm内存时就被执行了,所以上面在调driversIterator.next()方法加载mysql 的驱动实现到jvm内存时,已经将下面这段逻辑执行了,也就初始化了registeredDrivers列表
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

    /**
     * Construct a new driver and register it with DriverManager
     * 
     * @throws SQLException
     *             if a database error occurs.
     */
    public Driver() throws SQLException {
        // Required for Class.forName().newInstance()
    }
}

第三部分贴的代码有点多,要耐心看代码看注释;
这里总结一下第三部分的总思路是: 获取ServiceLoader实例,遍历ServiceLoader中的Iterator,Iterator的hasNext会加载配置文件,Iterator的next方法会将接口的实现加载到jvm内存中,加载接口实现到jvm内存时会执行静态代码块,静态代码块会初始化registeredDrivers策略列表,registeredDrivers策略列表在getConnection时就发挥策略作用了

你可能感兴趣的:(Java,SPI)