一、tomcat是個web容器,要解決以下問題
1. 一個web容器可能要部署兩個或者多個應用程序,不同的應用程序,可能會依賴同一個第三方類庫的不同版本,因此要保證每一個應用程序的類庫都是獨立、相互隔離的。
2. 部署在同一個web容器中的相同類庫的相同版本可以共享,否則,會有重復的類庫被加載進JVM
3. web容器也有自己的類庫,不能和應用程序的類庫混淆,需要相互隔離
4. web容器支持jsp文件修改后不用重啟,jsp文件也是要編譯成.class文件的,支持HotSwap功能
二、tomcat使用Java默認類加載器的問題
1. 默認的類加載器無法加載兩個相同類庫的不同版本,它只在乎類的全限定類名,並且只有一份,所以無法解決上面1和3,相互隔離的問題
2. 修改jsp文件后,因為類名一樣,默認的類加載器不會重新加載,而是使用方法區中已經存在的類;所以需要每個jsp對應一個唯一的類加載器,當修改jsp的時候,直接卸載唯一的類加載器,然后重新創建類加載器,並加載jsp文件
三、tomcat的類加載機制
1. 架構圖
2. tomcat自己定義的類加載器:
CommonClassLoader:tomcat最基本的類加載器,加載路徑中的class可以被tomcat和各個webapp訪問
CatalinaClassLoader:tomcat私有的類加載器,webapp不能訪問其加載路徑下的class,即對webapp不可見
SharedClassLoader:各個webapp共享的類加載器,對tomcat不可見
WebappClassLoader:webapp私有的類加載器,只對當前webapp可見
JspClassLoader
3. 每一個web應用程序對應一個WebappClassLoader,每一個jsp文件對應一個JspClassLoader,所以這兩個類加載器有多個實例
4. 工作原理:
a. CommonClassLoader能加載的類都可以被Catalina ClassLoader和SharedClassLoader使用,從而實現了公有類庫的共用
b. CatalinaClassLoader和Shared ClassLoader自己能加載的類則與對方相互隔離
c. WebAppClassLoader可以使用SharedClassLoader加載到的類,但各個WebAppClassLoader實例之間相互隔離,多個WebAppClassLoader是同級關系
d. 而JasperLoader的加載范圍僅僅是這個JSP文件所編譯出來的那一個.Class文件,它出現的目的就是為了被丟棄:當Web容器檢測到JSP文件被修改時,會替換掉目前的JasperLoader的實例,並通過再建立一個新的Jsp類加載器來實現JSP文件的HotSwap功能
5. tomcat目錄結構,與上面的類加載器對應
/common/*
/server/*
/shared/*
/WEB-INF/*
6. 默認情況下,conf目錄下的catalina.properties文件,沒有指定server.loader以及shared.loader,所以tomcat沒有建立CatalinaClassLoader和SharedClassLoader的實例,這兩個都會使用CommonClassLoader來代替。Tomcat6之后,把common、shared、server目錄合成了一個lib目錄。所以在我們的服務器里看不到common、shared、server目錄。
四、tomcat類加載器和雙親委派模型的關系
1. tomcat為了實現隔離性和熱替換,沒有使用默認的類加載器,而是自己實現了類加載器:
每個webappClassLoader加載自己目錄下的class文件
每個jasper類加載器加載一個jsp文件
2. 雙親委派模型的標准是:每個類加載器要加載類的時候,先傳給父類加載器加載,父類加載器加載不了的時候,才由自己加載
3. webappClassLoader和jasperClassLoader沒有傳給父類加載器去加載,還是傳給了父類加載器而父類加載器加載不了?先自己加載
4. 從WebappClassLoader.loadClass源碼上看,確實沒有傳給父類加載器去加載,確實破壞了雙親委派模型,對於一些未加載的非基礎類(非Object,String等),各個web應用自己的類加載器(WebAppClassLoader)會優先加載,加載不到時再交給commonClassLoader走雙親委托
hasExternalRepositories && searchExternalFirst 默認為false
5. 為什么要破壞?不破壞行不行?每個webapp有自己的目錄和類庫,比如一個webapp使用類庫A1.0版本,一個webapp使用類庫A2.0版本,父類加載器加載類庫A1.0版本,如果使用雙親委派,會由commonClassLoader去加載類庫A1.0版本,這樣第二個webapp會有問題
6. 可以通過在Context.xml文件中加上<Loader delegate = "true">
開啟正統的“雙親委派”加載機制
public Class<?> findClass(String name) throws ClassNotFoundException { // 其他代碼略去..... // Ask our superclass to locate this class, if possible // (throws ClassNotFoundException if it is not found) Class<?> clazz = null; try { if (log.isTraceEnabled()) log.trace(" findClassInternal(" + name + ")"); // (1)默認為false if (hasExternalRepositories && searchExternalFirst) { try { clazz = super.findClass(name); } catch(ClassNotFoundException cnfe) { // Ignore - will search internal repositories next } catch(AccessControlException ace) { log.warn("WebappClassLoaderBase.findClassInternal(" + name + ") security exception: " + ace.getMessage(), ace); throw new ClassNotFoundException(name, ace); } catch (RuntimeException e) { if (log.isTraceEnabled()) log.trace(" -->RuntimeException Rethrown", e); throw e; } } // (2) if ((clazz == null)) { try { clazz = findClassInternal(name); } catch(ClassNotFoundException cnfe) { if (!hasExternalRepositories || searchExternalFirst) { throw cnfe; } } catch(AccessControlException ace) { log.warn("WebappClassLoaderBase.findClassInternal(" + name + ") security exception: " + ace.getMessage(), ace); throw new ClassNotFoundException(name, ace); } catch (RuntimeException e) { if (log.isTraceEnabled()) log.trace(" -->RuntimeException Rethrown", e); throw e; } } //其他代碼略去........ return (clazz); }
加載過程:
- 先在本地緩存中查找是否已經加載過該類(對於一些已經加載了的類,會被緩存在
resourceEntries
這個數據結構中),如果已經加載即返回,否則 繼續下一步。 - 讓系統類加載器(AppClassLoader)嘗試加載該類,主要是為了防止一些基礎類會被web中的類覆蓋,如果加載到即返回,返回繼續。
- 前兩步均沒加載到目標類,那么web應用的類加載器將自行加載,如果加載到則返回,否則繼續下一步。
- 最后還是加載不到的話,則委托父類加載器(Common ClassLoader)去加載。
五、其他破壞了雙親委派模型的技術
1. OSGI是基於Java語言的動態模塊化規范,類加載器之間是網狀結構,更加靈活,但是也更復雜
2. JNDI服務,使用線程上線文類加載器,父類加載器去使用子類加載器
參考文檔:
https://blog.csdn.net/qq_38182963/article/details/78660779
https://blog.csdn.net/moakun/article/details/80563505
https://www.cnblogs.com/aspirant/p/8991830.html