點擊上方 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 中爲併發而生的 ConcurrentHashMap•Redis性能監控指標彙總•最全的DevOps工具集合,再也不怕選型了!•微服務架構下,解決數據庫跨庫查詢的一些思路•聊聊大廠面試官必問的 MySQL 鎖機制
關注我
喜歡就點個"在看"唄^_^
本文分享自微信公衆號 - IT牧場(itmuch_com)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。