关于JAVA的SPI机制,JDBC实例

一、SPI是什么?

  SPI 的全称为 (Service Provider Interface),是 JDK 内置的一种服务提供发现机制。比如JAVA中定义了jdbc的规范,然后由不同的厂商去落地实现该规范(也就是服务),然后可以通过ServiceLoader去加载这些服务。通过SPI机制可以实现模块插拔,比如当前使用的数据库是oracle,如果想切换为MySQL的话只需要将对应的jar包切换为MySQL的即可。(当然对应的sql语句可能会出问题,除非使用的是Hibernate这种不依赖具体关系型数据库的框架)

 

二、自己实现一个SPI

  1.定义接口(规范):

package com.xiezy.spi;
public interface SPITest {
    public String say(String str);
}

  2.实现接口(服务)

package com.xiezy.spi;
public class SPITestImpl implements SPITest {
    @Override
    public String say(String str) {
        return "SPI---" + str;
    }
}

  3.在服务的META-INF/Services目录下创建一个名称为接口全限定名,内容为实现类的全限定名。在本例中名称为:com.xiezy.spi.SPITest

com.xiezy.spi.SPITestImpl

  4.测试类

package com.xiezy.spi;

import java.util.ServiceLoader;

public class SPITestMain {
    public static void main(String[] args) {
        ServiceLoader<SPITest> spiTests = ServiceLoader.load(SPITest.class);
        for (SPITest spiTest : spiTests) {
            System.out.println(spiTest.say("haha~"));
        }
    }
}

  

三、实际例子(JDBC)

  相信大家在刚学习jdbc时的使用步骤都是先通过Class.forName("dirver")将对应的Driver加载进JVM才可以使用,但是现在是不需要的。现在我们可以像下面直接使用:

  这是因为SPI机制已经将需要的Driver自动加载了,可以看一下DriverManager.getConnection的源码(只截取重要部分):

private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        /**
        ...
        **/
        //遍历已经注册的Dirver,并尝试去获取连接
        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());
            }
        }
    }    

  可以看到是遍历registeredDrivers,如何去获取连接。那么这个registeredDrivers是什么时候赋值的呢?通过源码可以看到有如下方法:

public static synchronized void registerDriver(java.sql.Driver driver)
        throws SQLException {

        registerDriver(driver, null);
    }

  但是没看到在什么地方调用了该方法,在了解该方法究竟在哪调用前。我们先看一下DriverManager的static块

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


    private static void loadInitialDrivers() {
        /**
          ...
        **/
        public Void run() {
            //通过SPI机制加载Driver
            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;
         /**
          ...
        **/
}    

  可以看到DriverManager在初始化的时候去加载了Driver的实现类,比如本例子中使用的MySql包

   我们在看一下com.mysql.cj.jdbc.Driver初始化过程

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    //
    // Register ourselves with the DriverManager
    //
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }
 }

  可以看到在初始化的时候调用了registeredDrivers方法,所以我们知道了DriverManager中的registeredDrivers属性是在这时候赋值的。

 

posted @ 2020-06-11 17:48  永动机蜗牛  阅读(115)  评论(0)    收藏  举报