Java SPI機制

前言

雖然我自己在前段時間再總結一些Java知識,但是經過最近的面試發現,很多自己掌握的並不牢靠,所以決定把原來很多內容拆分出來一部分一部分自己寫,這篇主要在梳理一遍Java的SPI 機制吧。溫故而知新,可以爲師矣。


介紹

Java SPI 全程爲 Service Provider Interface,直譯過來就是 服務提供商接口。我理解的概念的話就是,由JDK語言開發組制定一系列功能接口,但功能的具體實現是由各個服務商自行提供。這也滿足的依賴倒置原則。依賴倒置原則的核心就是要我們面向接口編程,理解了面向接口編程。

具體事例

最熟悉的SPI服務應該就是JDBC了

在java.util.sql中定義了對於數據庫功能的各個接口,以及數據庫操作生命週期中各對象的接口

如Driver表示數據庫的驅動器,Connection表示一次數據庫的連接

我們在使用第三方實現的時候我們一般是直接通過java.util.sql中的DriverManager來獲取具體的第三方Driver或者Connection,那麼DriverManager是如何加載到第三方數據庫的呢?

接下來我們就好好梳理一下從接口Class文件加載到第三方實現的加載,以及第三方實現的調用的完整流程,看一下DriverManager的源碼

環境

jdk1.8.0_144

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>

加載SPI第三方實現

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

 

    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;
        }
​
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
​
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
        //.... 此處省略一些不重要的內容
​
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
         //關鍵是這裏的Class.forName(aDriver,true,ClassLoader.getSystemClassLoader())
         //此處使用ClassLoader.getSystemClassLoader()直接使用了AppClassLoader來加載具體實現類,打破了雙親委派機制
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }

 

再看一下第三方的Driver實現類中都幹了些什麼,此處以com.mysql.jdbc的Driver爲例

   
 static {
        try {
        //將自己的Driver實例註冊進java.util.sql.DriverManager的CopyOnWriteArrayList列表中,供後期使用
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }

 

當SPI接口調用第三方的功能實現,此處以JDBC的getConnection爲例

      try {
            DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test","test","123456");
        } catch (SQLException e) {
            e.printStackTrace();
        }

 

    @CallerSensitive
    public static Connection getConnection(String url)
        throws SQLException {
​
        java.util.Properties info = new java.util.Properties();
        //此處返回的是直接調用DriverManager的類,一般爲我們自己的程序類
        return (getConnection(url, info, Reflection.getCallerClass()));
    }

 

    private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        
        //此處爲我們自己程序類的類加載器 一般爲AppClassLoader
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized(DriverManager.class) {
            // synchronize loading of the correct classloader.
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }
​
        //此處省略一些不重要的內容
​
        for(DriverInfo aDriver : registeredDrivers) {
            
            //由AppClassLoader檢查是否已經裝載了第三方驅動類
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    Connection con = aDriver.driver.connect(url, info);
                   
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }
​
            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }
​
        }
        //此處省略一些不重要的內容
       
    }

總結

到目前爲止我們已經弄清楚,由BootstrapClassloader加載的協議接口類,如何打破雙親委派機制 來加載AppClassLoader纔可以加載到的classpath中的第三方實現類

主要方式爲

  1. 使用ClassLoader.getSystemClassLoader來獲取AppClassLoader

     Class.forName(aDriver, true,ClassLoader.getSystemClassLoader());
  1. 雖然實際使用中沒有用到,使用Thread.currentThread().getContextClassLoader()獲取AppClassLoader

    synchronized(DriverManager.class) {
        // synchronize loading of the correct classloader.
        if (callerCL == null) {
            callerCL = Thread.currentThread().getContextClassLoader();
        }
    }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章