我們經(jīng)常會(huì )遇到ClassNotFound異常,表明JVM在嘗試加載某類(lèi)時(shí)失敗了。
要解決這個(gè)異常,你得知道
想想Tomcat又是如何加載和管理Web應用下的Servlet呢?
Tomcat正是通過(guò)Context組件來(lái)加載管理Web應用的,所以今天我會(huì )詳細分析Tomcat的類(lèi)加載機制。但在這之前,我們有必要預習一下JVM的類(lèi)加載機制,我會(huì )先回答一下一開(kāi)始拋出來(lái)的問(wèn)題,接著(zhù)再談?wù)凾omcat的類(lèi)加載器如何打破Java的雙親委托機制。
Java的類(lèi)加載,就是把字節碼格式.class文件加載到JVM的方法區,并在JVM堆建立一個(gè)java.lang.Class對象實(shí)例,封裝Java類(lèi)相關(guān)的數據和方法。
Class對象是什么?
可以理解成業(yè)務(wù)類(lèi)的模板,JVM根據該模板創(chuàng )建具體業(yè)務(wù)類(lèi)對象實(shí)例。
JVM并非在啟動(dòng)時(shí)就把所有 .class 文件都加載一遍,而是程序在運行過(guò)程中用到該類(lèi)才去加載。
JVM類(lèi)加載由類(lèi)加載器完成,JDK提供一個(gè)抽象類(lèi)ClassLoader:
public abstract class ClassLoader { // 每個(gè)類(lèi)加載器都有個(gè)父加載器 private final ClassLoader parent; public Class<?> loadClass(String name) { // 查找該類(lèi)是否被加載過(guò) Class<?> c = findLoadedClass(name); // 若未被加載過(guò) if( c == null ){ // 【遞歸】委托給父加載器加載 if (parent != null) { c = parent.loadClass(name); } else { // 若父加載器為空,查找Bootstrap加載器是否加載過(guò)了 c = findBootstrapClassOrNull(name); } } // 若父加載器未加載成功,調用自己的findClass去加載 if (c == null) { c = findClass(name); } return c; } protected Class<?> findClass(String name){ // 1. 根據傳入的類(lèi)名name,到在特定目錄下去尋找類(lèi)文件,把.class文件讀入內存 ... // 2. 調用defineClass將字節數組轉成Class對象 return defineClass(buf, off, len); } // 將字節碼數組解析成一個(gè)Class對象,用native方法實(shí)現 protected final Class<?> defineClass(byte[] b, int off, int len){ ... } }
JVM的類(lèi)加載器是分層的父子關(guān)系,每個(gè)類(lèi)加載器都持有一個(gè)parent字段指向父加載器。
loadClass 首先檢查這個(gè)類(lèi)是不是已經(jīng)被加載過(guò)了,如果加載過(guò)了直接返回,否則交給父加載器去加載。
這是個(gè)遞歸調用,即子加載器持有父加載器引用,當一個(gè)類(lèi)加載器需加載一個(gè)Java類(lèi)時(shí),會(huì )先委托父加載器去加載,然后父加載器在自己加載路徑中搜索Java類(lèi),當父加載器在自己的加載范圍內找不到時(shí),才會(huì )交還給子加載器加載,這就是雙親委托機制。
JDK的類(lèi)加載器工作原理是一樣的,區別只是加載路徑不同,即findClass查找的路徑不同。
雙親委托機制是為保證一個(gè)Java類(lèi)在JVM的唯一性。假如你手滑寫(xiě)個(gè)與JRE核心類(lèi)同名類(lèi),比如Object,雙親委托機制能保證加載的是JRE里的那個(gè)Object類(lèi),而不是你寫(xiě)的Object。
因為AppClassLoader在加載你的Object類(lèi)時(shí),會(huì )委托給ExtClassLoader去加載,而ExtClassLoader又會(huì )委托給BootstrapClassLoader,BootstrapClassLoader發(fā)現自己已經(jīng)加載過(guò)了Object類(lèi),會(huì )直接返回,不會(huì )去加載你的Object類(lèi)。
類(lèi)加載器的父子關(guān)系不是通過(guò)繼承來(lái)實(shí)現的,比如AppClassLoader并非ExtClassLoader的子類(lèi),只是AppClassLoader的parent指向ExtClassLoader對象。
所以若自定義類(lèi)加載器,不是去繼承AppClassLoader,而是繼承ClassLoader抽象類(lèi),再重寫(xiě)findClass和loadClass即可。
Tomcat就是通過(guò)自定義類(lèi)加載器實(shí)現自己的類(lèi)加載。
若你要打破雙親委托,也就只需重寫(xiě)loadClass,因為loadClass的默認實(shí)現就是雙親委托機制。
Tomcat的自定義類(lèi)加載器WebAppClassLoader打破了雙親委托機制:
首先自己嘗試去加載某個(gè)類(lèi),如果找不到再委托給父類(lèi)加載器,目的是優(yōu)先加載Web應用自己定義的類(lèi)。
只需重寫(xiě)ClassLoader的兩個(gè)方法:
public Class<?> findClass(String name) throws ClassNotFoundException { ... Class<?> clazz = null; try { //1. 先在Web應用目錄下查找類(lèi) clazz = findClassInternal(name); } catch (RuntimeException e) { throw e; } if (clazz == null) { try { //2. 如果在本地目錄沒(méi)有找到,交給父加載器去查找 clazz = super.findClass(name); } catch (RuntimeException e) { throw e; } //3. 如果父類(lèi)也沒(méi)找到,拋出ClassNotFoundException if (clazz == null) { throw new ClassNotFoundException(name); } return clazz; }
工作流程
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { Class<?> clazz = null; //1. 先在本地cache查找該類(lèi)是否已經(jīng)加載過(guò) clazz = findLoadedClass0(name); if (clazz != null) { if (resolve) resolveClass(clazz); return clazz; } //2. 從系統類(lèi)加載器的cache中查找是否加載過(guò) clazz = findLoadedClass(name); if (clazz != null) { if (resolve) resolveClass(clazz); return clazz; } // 3. 嘗試用ExtClassLoader類(lèi)加載器類(lèi)加載,為什么? ClassLoader javaseLoader = getJavaseClassLoader(); try { clazz = javaseLoader.loadClass(name); if (clazz != null) { if (resolve) resolveClass(clazz); return clazz; } } catch (ClassNotFoundException e) { // Ignore } // 4. 嘗試在本地目錄搜索class并加載 try { clazz = findClass(name); if (clazz != null) { if (resolve) resolveClass(clazz); return clazz; } } catch (ClassNotFoundException e) { // Ignore } // 5. 嘗試用系統類(lèi)加載器(也就是AppClassLoader)來(lái)加載 try { clazz = Class.forName(name, false, parent); if (clazz != null) { if (resolve) resolveClass(clazz); return clazz; } } catch (ClassNotFoundException e) { // Ignore } } //6. 上述過(guò)程都加載失敗,拋出異常 throw new ClassNotFoundException(name); }
工作流程
可見(jiàn) Tomcat 類(lèi)加載器打破了雙親委托,沒(méi)有一上來(lái)就直接委托給父加載器,而是先在本地目錄下加載。
但為避免本地目錄類(lèi)覆蓋JRE核心類(lèi),會(huì )先嘗試用ExtClassLoader加載。
那為何不先用AppClassLoader加載?
若這樣,就又變成雙親委托,這就是Tomcat類(lèi)加載器的奧妙。
到此這篇關(guān)于淺談Tomcat如何打破雙親委托機制的文章就介紹到這了,更多相關(guān)Tomcat 雙親委托機制內容請搜索腳本之家以前的文章或繼續瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自本網(wǎng)站內容采集于網(wǎng)絡(luò )互聯(lián)網(wǎng)轉載等其它媒體和分享為主,內容觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如侵犯了原作者的版權,請告知一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容,聯(lián)系我們QQ:712375056,同時(shí)歡迎投稿傳遞力量。
Copyright ? 2009-2022 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 特網(wǎng)科技 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 百度云 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站