Java SPI機(jī)制

前言

雖然我自己在前段時間再總結(jié)一些Java知識,但是經(jīng)過最近的面試發(fā)現(xiàn),很多自己掌握的并不牢靠,所以決定把原來很多內(nèi)容拆分出來一部分一部分自己寫,這篇主要在梳理一遍Java的SPI 機(jī)制吧。溫故而知新,可以為師矣。


介紹

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

具體事例

最熟悉的SPI服務(wù)應(yīng)該就是JDBC了

在java.util.sql中定義了對于數(shù)據(jù)庫功能的各個接口,以及數(shù)據(jù)庫操作生命周期中各對象的接口

如Driver表示數(shù)據(jù)庫的驅(qū)動器,Connection表示一次數(shù)據(jù)庫的連接

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

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

環(huán)境

jdk1.8.0_144

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

加載SPI第三方實(shí)現(xiàn)

    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();
        //.... 此處省略一些不重要的內(nèi)容

        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
         //關(guān)鍵是這里的Class.forName(aDriver,true,ClassLoader.getSystemClassLoader())
         //此處使用ClassLoader.getSystemClassLoader()直接使用了AppClassLoader來加載具體實(shí)現(xiàn)類,打破了雙親委派機(jī)制
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }

再看一下第三方的Driver實(shí)現(xiàn)類中都干了些什么,此處以com.mysql.jdbc的Driver為例

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

當(dāng)SPI接口調(diào)用第三方的功能實(shí)現(xiàn),此處以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();
        //此處返回的是直接調(diào)用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();
            }
        }

        //此處省略一些不重要的內(nèi)容

        for(DriverInfo aDriver : registeredDrivers) {
            
            //由AppClassLoader檢查是否已經(jīng)裝載了第三方驅(qū)動類
            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());
            }

        }
        //此處省略一些不重要的內(nèi)容
       
    }

總結(jié)

到目前為止我們已經(jīng)弄清楚,由BootstrapClassloader加載的協(xié)議接口類,如何打破雙親委派機(jī)制 來加載AppClassLoader才可以加載到的classpath中的第三方實(shí)現(xiàn)類

主要方式為

  1. 使用ClassLoader.getSystemClassLoader來獲取AppClassLoader
      Class.forName(aDriver, true,ClassLoader.getSystemClassLoader());
  1. 雖然實(shí)際使用中沒有用到,使用Thread.currentThread().getContextClassLoader()獲取AppClassLoader
    synchronized(DriverManager.class) {
        // synchronize loading of the correct classloader.
        if (callerCL == null) {
            callerCL = Thread.currentThread().getContextClassLoader();
        }
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 主要回顧了java的類加載機(jī)制,servlet3.0新特性,java的spi機(jī)制,以及spring-mvc的初始化...
    進(jìn)擊de大黃閱讀 7,843評論 0 18
  • 本文以JDBC為例深入講解 java spi 機(jī)制,將幫助你理解:什么是SPI,SPI實(shí)現(xiàn)原理,SPI的使用和SP...
    匠丶閱讀 5,308評論 0 8
  • 概述 ??在某些時候我們可以通過在軟件上游提供服務(wù)接口,無需在意接口的實(shí)現(xiàn)邏輯,全部交由擴(kuò)展程序進(jìn)行實(shí)現(xiàn),上游只需...
    ruoshy閱讀 703評論 0 6
  • 月考考完了,老師把語文試卷發(fā)了下來,看到了分?jǐn)?shù),我考的很不理想。 第二大題,看拼音,寫詞語,化妝...
    俊浩閱讀 1,148評論 0 1
  • 我們上學(xué)的時候,一下課,跑到操場上,跳跳格子,踢踢毽子。上課鈴聲響起,小手拍著嘴“喔喔喔”表示暫停。下了這節(jié)課出來...
    合百合閱讀 870評論 11 18

友情鏈接更多精彩內(nèi)容