您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“Tomcat是如何打破雙親委托機(jī)制的”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
JVM的類加載器
Tomcat的類加載器
findClass
loadClass
我們經(jīng)常會(huì)遇到ClassNotFound異常,表明JVM在嘗試加載某類時(shí)失敗了。
要解決這個(gè)異常,你得知道
什么是類加載
JVM如何加載類
為什么會(huì)出現(xiàn)ClassNotFound
想想Tomcat又是如何加載和管理Web應(yīng)用下的Servlet呢?
Tomcat正是通過(guò)Context組件來(lái)加載管理Web應(yīng)用的,所以今天我會(huì)詳細(xì)分析Tomcat的類加載機(jī)制。但在這之前,我們有必要預(yù)習(xí)一下JVM的類加載機(jī)制,我會(huì)先回答一下一開始拋出來(lái)的問(wèn)題,接著再談?wù)凾omcat的類加載器如何打破Java的雙親委托機(jī)制。
Java的類加載,就是把字節(jié)碼格式.class文件加載到JVM的方法區(qū),并在JVM堆建立一個(gè)java.lang.Class對(duì)象實(shí)例,封裝Java類相關(guān)的數(shù)據(jù)和方法。
Class對(duì)象是什么?
可以理解成業(yè)務(wù)類的模板,JVM根據(jù)該模板創(chuàng)建具體業(yè)務(wù)類對(duì)象實(shí)例。
JVM并非在啟動(dòng)時(shí)就把所有 .class 文件都加載一遍,而是程序在運(yùn)行過(guò)程中用到該類才去加載。
JVM類加載由類加載器完成,JDK提供一個(gè)抽象類ClassLoader:
public abstract class ClassLoader { // 每個(gè)類加載器都有個(gè)父加載器 private final ClassLoader parent; public Class<?> loadClass(String name) { // 查找該類是否被加載過(guò) Class<?> c = findLoadedClass(name); // 若未被加載過(guò) if( c == null ){ // 【遞歸】委托給父加載器加載 if (parent != null) { c = parent.loadClass(name); } else { // 若父加載器為空,查找Bootstrap加載器是否加載過(guò)了 c = findBootstrapClassOrNull(name); } } // 若父加載器未加載成功,調(diào)用自己的findClass去加載 if (c == null) { c = findClass(name); } return c; } protected Class<?> findClass(String name){ // 1. 根據(jù)傳入的類名name,到在特定目錄下去尋找類文件,把.class文件讀入內(nèi)存 ... // 2. 調(diào)用defineClass將字節(jié)數(shù)組轉(zhuǎn)成Class對(duì)象 return defineClass(buf, off, len); } // 將字節(jié)碼數(shù)組解析成一個(gè)Class對(duì)象,用native方法實(shí)現(xiàn) protected final Class<?> defineClass(byte[] b, int off, int len){ ... } }
JVM的類加載器是分層的父子關(guān)系,每個(gè)類加載器都持有一個(gè)parent字段指向父加載器。
defineClass 工具方法:調(diào)用native方法把Java類的字節(jié)碼解析成一個(gè)Class對(duì)象
findClass 就是找到 .class 文件,可能來(lái)自文件系統(tǒng)或網(wǎng)絡(luò),找到后把 .class 文件讀到內(nèi)存得到字節(jié)碼數(shù)組,然后調(diào)用defineClass方法得到Class對(duì)象
loadClass 首先檢查這個(gè)類是不是已經(jīng)被加載過(guò)了,如果加載過(guò)了直接返回,否則交給父加載器去加載。
這是個(gè)遞歸調(diào)用,即子加載器持有父加載器引用,當(dāng)一個(gè)類加載器需加載一個(gè)Java類時(shí),會(huì)先委托父加載器去加載,然后父加載器在自己加載路徑中搜索Java類,當(dāng)父加載器在自己的加載范圍內(nèi)找不到時(shí),才會(huì)交還給子加載器加載,這就是雙親委托機(jī)制。
JDK的類加載器工作原理是一樣的,區(qū)別只是加載路徑不同,即findClass查找的路徑不同。
雙親委托機(jī)制是為保證一個(gè)Java類在JVM的唯一性。假如你手滑寫個(gè)與JRE核心類同名類,比如Object,雙親委托機(jī)制能保證加載的是JRE里的那個(gè)Object類,而不是你寫的Object。
因?yàn)锳ppClassLoader在加載你的Object類時(shí),會(huì)委托給ExtClassLoader去加載,而ExtClassLoader又會(huì)委托給BootstrapClassLoader,BootstrapClassLoader發(fā)現(xiàn)自己已經(jīng)加載過(guò)了Object類,會(huì)直接返回,不會(huì)去加載你的Object類。
類加載器的父子關(guān)系不是通過(guò)繼承來(lái)實(shí)現(xiàn)的,比如AppClassLoader并非ExtClassLoader的子類,只是AppClassLoader的parent指向ExtClassLoader對(duì)象。
所以若自定義類加載器,不是去繼承AppClassLoader,而是繼承ClassLoader抽象類,再重寫findClass和loadClass即可。
Tomcat就是通過(guò)自定義類加載器實(shí)現(xiàn)自己的類加載。
若你要打破雙親委托,也就只需重寫loadClass,因?yàn)閘oadClass的默認(rèn)實(shí)現(xiàn)就是雙親委托機(jī)制。
Tomcat的自定義類加載器WebAppClassLoader打破了雙親委托機(jī)制:
首先自己嘗試去加載某個(gè)類,如果找不到再委托給父類加載器,目的是優(yōu)先加載Web應(yīng)用自己定義的類。
只需重寫ClassLoader的兩個(gè)方法:
public Class<?> findClass(String name) throws ClassNotFoundException { ... Class<?> clazz = null; try { //1. 先在Web應(yīng)用目錄下查找類 clazz = findClassInternal(name); } catch (RuntimeException e) { throw e; } if (clazz == null) { try { //2. 如果在本地目錄沒有找到,交給父加載器去查找 clazz = super.findClass(name); } catch (RuntimeException e) { throw e; } //3. 如果父類也沒找到,拋出ClassNotFoundException if (clazz == null) { throw new ClassNotFoundException(name); } return clazz; }
工作流程
先在Web應(yīng)用本地目錄下查找要加載的類
若未找到,交給父加載器查找,即AppClassLoader
若父加載器也沒找到這個(gè)類,拋ClassNotFound
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { Class<?> clazz = null; //1. 先在本地cache查找該類是否已經(jīng)加載過(guò) clazz = findLoadedClass0(name); if (clazz != null) { if (resolve) resolveClass(clazz); return clazz; } //2. 從系統(tǒng)類加載器的cache中查找是否加載過(guò) clazz = findLoadedClass(name); if (clazz != null) { if (resolve) resolveClass(clazz); return clazz; } // 3. 嘗試用ExtClassLoader類加載器類加載,為什么? 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. 嘗試用系統(tǒng)類加載器(也就是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); }
工作流程
先在本地Cache查找該類是否已加載過(guò)
即Tomcat的類加載器是否已經(jīng)加載過(guò)這個(gè)類。
若Tomcat類加載器尚未加載過(guò)該類,再看看系統(tǒng)類加載器是否加載過(guò)
若都沒有,就讓ExtClassLoader加載,為防止Web應(yīng)用自己的類覆蓋JRE的核心類
因?yàn)門omcat需打破雙親委托,假如Web應(yīng)用里自定義了一個(gè)叫Object的類,若先加載該Object類,就會(huì)覆蓋JRE的Object類,所以Tomcat類加載器優(yōu)先嘗試用ExtClassLoader去加載,因?yàn)镋xtClassLoader會(huì)委托給BootstrapClassLoader去加載,BootstrapClassLoader發(fā)現(xiàn)自己已經(jīng)加載了Object類,直接返回給Tomcat的類加載器,這樣Tomcat的類加載器就不會(huì)去加載Web應(yīng)用下的Object類了,避免覆蓋JRE核心類。
若ExtClassLoader加載失敗,即JRE無(wú)此類,則在本地Web應(yīng)用目錄下查找并加載
若本地目錄下無(wú)此類,說(shuō)明不是Web應(yīng)用自己定義的類,那么由系統(tǒng)類加載器去加載。這里請(qǐng)你注意,Web應(yīng)用是通過(guò)Class.forName調(diào)用交給系統(tǒng)類加載器的,因?yàn)镃lass.forName的默認(rèn)加載器就是系統(tǒng)類加載器。
若上述加載過(guò)程都失敗,拋ClassNotFound
可見 Tomcat 類加載器打破了雙親委托,沒有一上來(lái)就直接委托給父加載器,而是先在本地目錄下加載。
但為避免本地目錄類覆蓋JRE核心類,會(huì)先嘗試用ExtClassLoader加載。
那為何不先用AppClassLoader加載?
若這樣,就又變成雙親委托,這就是Tomcat類加載器的奧妙。
“Tomcat是如何打破雙親委托機(jī)制的”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。