更新時(shí)間:2020-12-03 17:16:48 來源:動(dòng)力節(jié)點(diǎn) 瀏覽1603次
說到Tomcat類加載機(jī)制,我們不得不提及JVM的類加載,然后Tomcat也是運(yùn)行在JVM上的。所以,我們先拋磚引玉,一起來看看JVM類加載。
JVM類加載采用父類委托機(jī)制,當(dāng)JVM運(yùn)行過程中,用戶需要加載某些類時(shí),用戶自己的類加載器,把加載請(qǐng)求傳給父加載器,父加載器再傳給其父加載器,一直到加載器樹的頂層。最頂層的類加載器首先針對(duì)其特定的位置加載,如果加載不到就轉(zhuǎn)交給子類。如果一直到底層的類加載都沒有加載到,那么就會(huì)拋出異常ClassNotFoundException。
回過頭來,我們?cè)賮砜碩omcat類加載,Tomcat類加載機(jī)制是違反了雙親委托原則的,對(duì)于一些未加載的非基礎(chǔ)類(Object,String等),各個(gè)web應(yīng)用自己的類加載器(WebAppClassLoader)會(huì)優(yōu)先加載,加載不到時(shí)再交給commonClassLoader走雙親委托。
下面的簡(jiǎn)圖是Tomcat9版本的官方文檔給出的Tomcat的類加載器的圖。
????Bootstrap
??????????|
???????System
??????????|
???????Common
???????/????\
??Webapp1 ??Webapp2 ..
Bootstrap :是Java的最高的加載器,用C語(yǔ)言實(shí)現(xiàn),主要用來加載JVM啟動(dòng)時(shí)所需要的核心類,例如$JAVA_HOME/jre/lib/ext路徑下的類。
System: 會(huì)加載CLASSPATH系統(tǒng)變量所定義路徑的所有的類。
Common:會(huì)加載Tomcat路徑下的lib文件下的所有類。
Webapp1、Webapp2……: 會(huì)加載webapp路徑下項(xiàng)目中的所有的類。一個(gè)項(xiàng)目對(duì)應(yīng)一個(gè)WebappClassLoader,這樣就實(shí)現(xiàn)了應(yīng)用之間類的隔離了。
這3個(gè)部分,在上面的Java雙親委派模型圖中都有體現(xiàn)。不過可以看到ExtClassLoader沒有畫出來,可以理解為是跟bootstrap合并了,都是去JAVA_HOME/jre/lib下面加載類。那么Tomcat為什么要自定義類加載器呢?
隔離不同應(yīng)用:部署在同一個(gè)Tomcat中的不同應(yīng)用A和B,例如A用了Spring2.5。B用了Spring3.5,那么這兩個(gè)應(yīng)用如果使用的是同一個(gè)類加載器,那么Web應(yīng)用就會(huì)因?yàn)閖ar包覆蓋而無法啟動(dòng)。
靈活性:Web應(yīng)用之間的類加載器相互獨(dú)立,那么就可以根據(jù)修改不同的文件重建不同的類加載器替換原來的。從而不影響其他應(yīng)用。
性能:如果在一個(gè)Tomcat部署多個(gè)應(yīng)用,多個(gè)應(yīng)用中都有相同的類庫(kù)依賴。那么可以把這相同的類庫(kù)讓Common類加載器進(jìn)行加載。
Tomcat自定義了WebAppClassLoader類加載器。打破了雙親委派的機(jī)制,即如果收到類加載的請(qǐng)求,會(huì)嘗試自己去加載,如果找不到再交給父加載器去加載,目的就是為了優(yōu)先加載Web應(yīng)用自己定義的類。我們知道ClassLoader默認(rèn)的loadClass方法是以雙親委派的模型進(jìn)行加載類的,那么Tomcat既然要打破這個(gè)規(guī)則,就要重寫loadClass方法,我們可以看WebAppClassLoader類中重寫的loadClass方法。
@Override
public Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
????synchronized (getClassLoadingLock(name)) {
????????Class clazz = null;
// 1. 從本地緩存中查找是否加載過此類
????????clazz = findLoadedClass0(name);
????????if (clazz != null) {
????????????if (log.isDebugEnabled())
????????????????log.debug(" ?Returning class from cache");
????????????if (resolve)
????????????????resolveClass(clazz);
????????????return clazz;
????????}
// 2. 從AppClassLoader中查找是否加載過此類
????????clazz = findLoadedClass(name);
????????if (clazz != null) {
????????????if (log.isDebugEnabled())
????????????????log.debug(" ?Returning class from cache");
????????????if (resolve)
????????????????resolveClass(clazz);
????????????return clazz;
????????}
????????String resourceName = binaryNameToPath(name, false);
// 3. 嘗試用ExtClassLoader 類加載器加載類,防止Web應(yīng)用覆蓋JRE的核心類
????????ClassLoader javaseLoader = getJavaseClassLoader();
????????boolean tryLoadingFromJavaseLoader;
????????try {
????????????URL url;
????????????if (securityManager != null) {
????????????????PrivilegedActiondp = new PrivilegedJavaseGetResource(resourceName);
????????????????url = AccessController.doPrivileged(dp);
????????????} else {
????????????????url = javaseLoader.getResource(resourceName);
????????????}
????????????tryLoadingFromJavaseLoader = (url != null);
????????} catch (Throwable t) {
????????????tryLoadingFromJavaseLoader = true;
????????}
????????boolean delegateLoad = delegate || filter(name, true);
?// 4. 判斷是否設(shè)置了delegate屬性,如果設(shè)置為true那么就按照雙親委派機(jī)制加載類
????????if (delegateLoad) {
????????????if (log.isDebugEnabled())
????????????????log.debug(" ?Delegating to parent classloader1 " + parent);
????????????try {
????????????????clazz = Class.forName(name, false, parent);
????????????????if (clazz != null) {
????????????????????if (log.isDebugEnabled())
????????????????????????log.debug(" ?Loading class from parent");
????????????????????if (resolve)
????????????????????????resolveClass(clazz);
????????????????????return clazz;
????????????????}
????????????} catch (ClassNotFoundException e) {
????????????????// Ignore
????????????}
????????}
? // 5. 默認(rèn)是設(shè)置delegate是false的,那么就會(huì)先用WebAppClassLoader進(jìn)行加載
????????if (log.isDebugEnabled())
????????????log.debug(" ?Searching local repositories");
????????try {
????????????clazz = findClass(name);
????????????if (clazz != null) {
????????????????if (log.isDebugEnabled())
????????????????????log.debug(" ?Loading class from local repository");
????????????????if (resolve)
????????????????????resolveClass(clazz);
????????????????return clazz;
????????????}
????????} catch (ClassNotFoundException e) {
????????????// Ignore
????????}
?// 6. 如果此時(shí)在WebAppClassLoader沒找到類,那么就委托給AppClassLoader去加載
????????if (!delegateLoad) {
????????????if (log.isDebugEnabled())
????????????????log.debug(" ?Delegating to parent classloader at end: " + parent);
????????????try {
????????????????clazz = Class.forName(name, false, parent);
????????????????if (clazz != null) {
????????????????????if (log.isDebugEnabled())
????????????????????????log.debug(" ?Loading class from parent");
????????????????????if (resolve)
????????????????????????resolveClass(clazz);
????????????????????return clazz;
????????????????}
????????????} catch (ClassNotFoundException e) {
????????????????// Ignore
????????????}
????????}
????}
????throw new ClassNotFoundException(name);
}
我們總結(jié)起來就是Web應(yīng)用默認(rèn)的類加載順序是(打破了雙親委派規(guī)則):
1.先從JVM的BootStrapClassLoader中加載。
2.加載Web應(yīng)用下/WEB-INF/classes中的類。
3.加載Web應(yīng)用下/WEB-INF/lib/*.jap中的jar包中的類。
4.加載上面定義的System路徑下面的類。
5.加載上面定義的Common路徑下面的類。
到此為止,Tomcat類加載機(jī)制逐漸明朗,Tomcat類加載機(jī)制的重點(diǎn)就是打破了雙親委派機(jī)制,WebAppClassLoader加載類的時(shí)候,繞開 AppClassLoader,直接先使用 ExtClassLoader 來加載類。這不僅保證了基礎(chǔ)類不會(huì)被同時(shí)加載,也
保證了在同一個(gè) Tomcat 下不同 web 之間的 class 是相互隔離的。好了。Tomcat的類加載機(jī)制就講到這里,感興趣的小伙伴可以去觀看本站的Tomcat服務(wù)器教程,深入學(xué)習(xí)Tomcat里的各種技術(shù)。
0基礎(chǔ) 0學(xué)費(fèi) 15天面授
有基礎(chǔ) 直達(dá)就業(yè)
業(yè)余時(shí)間 高薪轉(zhuǎn)行
工作1~3年,加薪神器
工作3~5年,晉升架構(gòu)
提交申請(qǐng)后,顧問老師會(huì)電話與您溝通安排學(xué)習(xí)
初級(jí) 202925
初級(jí) 203221
初級(jí) 202629
初級(jí) 203743