一文分析特殊的類加載器”ServiceLoader”

點擊上方 IT牧場 ,選擇 置頂或者星標技術乾貨每日送達!

前言

    

最近在看類加載器相關的知識發現Class.forName(“XXX”)和ClassLoader.load(“xxx”)在類加載流程裏面是有比較大的區別。class.forName(“XXXX”)會執行加載-鏈接-初始化,而ClassLoader.load只會執行加載。


 如果我們寫Test測試可以發現,class.forName方式確實是可以走到初始化環節,而classLoder.load方式並不會。基於此想到基於原生jdbc的方式去操作db的步驟(本文基於jdk8 、mysql驅動包mysql-connector-java:5.1.34)去驗證。

驗證過程


jdbc操作db的方式 第一步是需要註冊數據庫驅動的。實例如下

public class Test1 {    public static void main(String[] args) throws Exception {        Class.forName(“com.mysql.jdbc.Driver”)         String url = "jdbc:mysql:/xxx?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL=true&verifyServerCertificate=false&allowMultiQueries=true";        String user = "xx";        String password = "xx.db.2019!";        Connection connection = DriverManager.getConnection(url, user, password);        PreparedStatement preparedStatement = connection.prepareStatement("SELECT 1 FROM DUAL ");        ResultSet resultSet = preparedStatement.executeQuery();        while (resultSet.next()) {            String string = resultSet.getString(1);            System.out.println(string);        }        connection.close();
}
}



Class.forName(“com.mysql.jdbc.Driver”) 會執行靜態代碼塊並註冊mysql的數據庫驅動



此時程序運行會如期返回結果。

再將第三行換成如下代碼

Thread.currentThread().getContextClassLoader().loadClass("com.mysql.jdbc.Driver");

發現結果依然能成功返回~

WTF ??


 兩次驗證結果證明類加載時機,跟之前所說不太一致。這就有點懵了,按理說第二次驗證應該提示錯誤纔對,因爲沒有顯式註冊驅動。於是再次驗證一個場景~

再次驗證


這裏做一個測試,如果將測試類中的註冊驅動代碼刪掉,測試代碼還能會繼續執行嗎?

public class Test1 {    public static void main(String[] args) throws Exception {
String url = "jdbc:mysql://xx?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL=true&verifyServerCertificate=false&allowMultiQueries=true"; String user = "xx"; String password = "xx"; Connection connection = DriverManager.getConnection(url, user, password); PreparedStatement preparedStatement = connection.prepareStatement("SELECT 1 FROM DUAL "); ResultSet resultSet = preparedStatement.executeQuery(); while (resultSet.next()) { String string = resultSet.getString(1); System.out.println(string); } connection.close();
}
}


答案是肯定的。

分析


這裏可能會疑問,爲什麼我不需要註冊驅動,但是程序能繼續執行?

答案要從DriverManager.getConnection()方法說起,當jvm執行此代碼時要做一件事。類加載流程,將DriverManager加載到jvm中,並執行初始化方法(靜態代碼塊)。DriverManager中的靜態代碼塊中代碼如下

 


這一行只是創建了一個ServiceLoader實例,注意ServiceLoader實現Iterable接口。所以要重寫iterator方法

 


lookupIterator是ServiceLoader的成員變量,也是ServiceLoader的內部類

 


LazyIterator纔是ServiceLoader的核心。因爲真正觸發類加載過程的就是迭代的過程。代碼複製如下


 private boolean hasNextService() {            if (nextName != null) {                return true;            }            if (configs == null) {                try {                //獲取文件全名 這裏是/META-INF/services/java.sql.Driver                    String fullName = PREFIX + service.getName();                    if (loader == null)                        configs = ClassLoader.getSystemResources(fullName);                    else                    //通過類加載器獲取到config對象,此時對象包含兩個jar包下的                    //文件,分別是mysql驅動包和druid數據源下驅動包                        configs = loader.getResources(fullName);                } catch (IOException x) {                    fail(service, "Error locating configuration files", x);                }            }            while ((pending == null) || !pending.hasNext()) {                if (!configs.hasMoreElements()) {                    return false;                }                //pending是通過上面獲取的文件,解析文件裏面的內容獲得                //驅動類的全名                pending = parse(service, configs.nextElement());            }            nextName = pending.next();            return true;        }


 對應mysql驅動包的加載文件如下

 


 

這些類名就是mysql驅動包的類全名。

LazyIterator迭代器的next()方法是真正開始將上面獲取的類名加載的jvm的過程

 


最終初始化完成的實例對象會存到ServiceLoader中的providers中

 

結論


此時疑惑得到解答,我在DriverManage的文檔註釋中也找到了答案。

 


 中間這段大概意思是當getConnection()方法被調用時,DriverManager會用當前應用的類加載器選擇一個合適的數據庫驅動加載。言外之意,新版本中不需要在顯示的註冊驅動了。


 這裏小夥伴可以瞭解下java中SPI機制。ServiceLoader是SPI機制的實現。可以通過定義接口,在不同的jar包實現接口,並以ServiceLoader規範的方式創建好相應文件。即可在應用中實現自動創建jar包中實現類的實例。這就是組件化,插件化實現的一種方式。

乾貨分享

最近將個人學習筆記整理成冊,使用PDF分享。關注我,回覆如下代碼,即可獲得百度盤地址,無套路領取!

001:《Java併發與高併發解決方案》學習筆記;002:《深入JVM內核——原理、診斷與優化》學習筆記;003:《Java面試寶典》004:《Docker開源書》005:《Kubernetes開源書》006:《DDD速成(領域驅動設計速成)》007:全部008:加技術羣討論

近期熱文

LinkedBlockingQueue vs ConcurrentLinkedQueue解讀Java 8 中爲併發而生的 ConcurrentHashMapRedis性能監控指標彙總最全的DevOps工具集合,再也不怕選型了!微服務架構下,解決數據庫跨庫查詢的一些思路聊聊大廠面試官必問的 MySQL 鎖機制

關注我

喜歡就點個"在看"唄^_^

本文分享自微信公衆號 - IT牧場(itmuch_com)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

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