實戰分享:Tomcat打破雙親委派模型,實現Web應用獨立與安全隔離的奧秘

目錄

一、JVM 類加載機制

二、Tomcat 類加載器

? ? ? ? 2.2 findClass 介紹

? ? ? ? 3.2 loadClass 介紹

三、web應用隔離

? ? ? ? 3.1 Spring 加載問題


? ? ? ? 在開始文章內容之前,先來看三個問題

  1. 假如在 Tomcat 上運行了兩個 Web 應用程序,兩個 web 應用中有同名的Servlet,比如都叫UserController,但是功能不同,Tomcat 需要同時加載和管理這兩個同名的 Servlet 類,保證他們不會沖突,那怎么才能實現隔離?
  2. 假如兩個 web 應用都依賴同一個第三方 jar 包,比如spring,那 spring 的 jar 包被加載到內存后,Tomcat 保證這兩個 web 應用能共享,也就是說 spring 的 jar 包只被加載一次,否則隨著依賴第三方的增多,JVM的內存會爆炸,這時怎么做到的?
  3. 跟JVM一樣,怎樣隔離 Tomcat 本身和 web 應用類?

? ? ? ? 以上三個問題本文會逐一來講解,下面先來看下 JVM 的類加載機制。

一、JVM 類加載機制

????????Java 的類加載就是把字節碼格式 “.class” 文件加載到 JVM 方法區,并在 JVM 的堆中建立一個 java.lang.class 對象實例,用來封裝 Java 類相關的數據和方法。

????????JVM 并不是在啟動時就把所有的 “.class” 文件都加載一遍,而是程序在運行過程中用到了這個類才去加載。JVM 類加載是由類加載器來完成的,JDK 提供一個抽象類 ClassLoader,這個抽象類中定義了三個關鍵方法理解清楚他們的作用和關系非常重要。

  • JVM 的類加載器是有層次結構的,他們有父子關系,每個類加載器都有一個 parent 字段,指向父類加載器
  • defineClass 是個工具方法,它的職責是調用 native 方法把 Java 類的字節碼解析成一個Class 對象,所謂的 native 方法就是由C語言實現的方法,Java通過 JNI 機制調用
  • findClass 方法的主要職責就是找到 “.class” 文件,可能來自文件系統或者網絡,找到后把“.class” 文件讀到內存得到字節碼,然后調用 defineClass 方法獲得 Class 對象
  • loadClass 是個 public 方法,說明它才是對外提供服務的接口,具體實現也比較清晰:首先檢查這個類是不是已經被加載過了,如果加載過了直接返回,否則交給父加載器去加載。注意,這是一個遞歸調用,也就是說子加載器持有父加載器的引用,當一個類加載器需要加載一個 Java 類時,會先委托父加載器去加載,然后父加載器在自己的加載路徑中搜索 Java 類,當父加載器在自己的加載范圍內找不到時,才會交還給子加載器加載,這就是雙親委托機制。
public abstract class ClassLoader {// 每個類加載器都有個父加載器private final ClassLoader parent;public Class<?> loadClass(String name) {// 查找一下這個類是不是已經加載過了Class<?> c = findLoadedClass(name);// 如果沒有加載過if( c == null ){// 先委托給父加載器去加載,注意這是個遞歸調用if (parent != null) {c = parent.loadClass(name);} else {// 如果父加載器為空,查找Bootstrap加載器是不是加載過了c = findBootstrapClassOrNull(name);}}// 如果父加載器沒加載成功,調用自己的findClass去加載if (c == null) {c = findClass(name);}return c;}protected Class<?> findClass(String name){//1. 根據傳入的類名name,到在特定目錄下去尋找類文件,把.class文件讀入內存...//2. 調用defineClass將字節數組轉成Class對象return defineClass(buf, off, len);}// 將字節碼數組解析成一個Class對象,用native方法實現protected final Class<?> defineClass(byte[] b, int off, int len){...}
}

? ? ? ? JVM 雙親委派如圖

  • BootstrapClassLoader 是啟動類加載器,由 C 語言實現,用來加載 JVM 啟動時所需要的核心類,比如 rt.jar、resource.jar 等。
  • ExtClassLoader 是擴展類加載器,用來接在 \jre\lib\ext 目錄下的 jar 包。
  • AppClassLoader 是系統類加載器,用來加載 classpath 下的類,應用程序默認用它來加載類。
  • 自定義類加載器,用來加載自定義路徑下的類。

????????這些類加載器的工作原理是一樣的,區別是他們的加載路徑不同,也就是說 findClass 這個方法查找的路徑不同。雙親委派機制是為了保證一個 Java 類在 JVM 中是唯一的,假如不小心寫了一個與 JRE 核心類同名的類,比如 Object 類,雙親委派機制能保證加載的是 JRE 里的那個Object 類,而不是你自己寫的 Object 類。這是因為 AppClassLoader 在加載你的 Object 類時,會委托給 ExtClassLoader 去加載,而 ExtClassLoader 又會委托給 BootstrapClassLoader,BootstrapClassLoader 發現自己已經加載過了 Object 類,會直接返回,不會去加載你寫的 Object 類。

????????注意,類加載器的父子關系不是通過繼承來實現的,比如 AppClassLoader 并不是 ExtClassLoader 的子類,而是說 AppClassLoader 的 parent 成員變量指向 ExtClassLoader 對象。

二、Tomcat 類加載器

????????Tomca t的自定義類加載器 WebAppClassLoader 打破了雙親委派機制,首先自己嘗試去加載某個類,如果找不到再代理給父類加載器,其目的是優先加載 Web 應用自己定義的類。具體實現就是重寫 ClassLoader 的兩個方法:findClass 和 loadClass。看下下面的源碼

? ? ? ? 2.2 findClass 介紹

public Class<?> findClass(String name) throws ClassNotFoundException {...Class<?> clazz = null;try {//1. 先在Web應用目錄下查找類 clazz = findClassInternal(name);}  catch (RuntimeException e) {throw e;}if (clazz == null) {try {//2. 如果在本地目錄沒有找到,交給父加載器去查找clazz = super.findClass(name);}  catch (RuntimeException e) {throw e;}//3. 如果父類也沒找到,拋出ClassNotFoundExceptionif (clazz == null) {throw new ClassNotFoundException(name);}return clazz;
}

?在 findClass 方法里,主要有三個步驟:

  1. 先在 Web 應用本地目錄下查找要加載的類。
  2. 如果沒有找到,交給父加載器去查找,它的父加載器就是上面提到的系統類加載器 AppClassLoader。
  3. 如果父加載器也沒找到這個類,拋出 ClassNotFound 異常。

? ? ? ? 3.2 loadClass 介紹

public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {Class<?> clazz = null;//1. 先在本地cache查找該類是否已經加載過clazz = findLoadedClass0(name);if (clazz != null) {if (resolve)resolveClass(clazz);return clazz;}//2. 從系統類加載器的cache中查找是否加載過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. 嘗試用系統類加載器(也就是AppClassLoader)來加載try {clazz = Class.forName(name, false, parent);if (clazz != null) {if (resolve)resolveClass(clazz);return clazz;}} catch (ClassNotFoundException e) {// Ignore}}//6. 上述過程都加載失敗,拋出異常throw new ClassNotFoundException(name);
}

????????loadClass 方法稍微復雜一點,主要有六個步驟:

  1. 先在本地 Cache 查找該類是否已經加載過,也就是說 Tomcat 的類加載器是否已經加載過這個類。
  2. 如果 Tomcat 類加載器沒有加載過這個類,再看看系統類加載器是否加載過。
  3. 如果都沒有,就讓 ExtClassLoader 去加載,這一步比較關鍵,目的防止 Web 應用自己的類覆蓋 JRE 的核心類。因為 Tomcat 需要打破雙親委派機制,假如 Web 應用里自定義了一個叫 Object 的類,如果先加載這個 Object 類,就會覆蓋 JRE 里面的那個 Object 類,這就是為什么 Tomcat 的類加載器會優先嘗試用 ExtClassLoader 去加載,因為 ExtClassLoader 會委托給 BootstrapClassLoader 去加載,BootstrapClassLoader 發現自己已經加載了 Object 類,直接返回給 Tomcat 的類加載器,這樣 Tomcat 的類加載器就不會去加載 Web 應用下的 Object 類了,也就避免了覆蓋 JRE 核心類的問題。
  4. 如果 ExtClassLoader 加載器加載失敗,也就是說 JRE 核心類中沒有這類,那么就在本地 Web 應用目錄下查找并加載。
  5. 如果本地目錄下沒有這個類,說明不是 Web 應用自己定義的類,那么由系統類加載器去加載。這里請你注意,Web 應用是通過Class.forName調用交給系統類加載器的,因為Class.forName的默認加載器就是系統類加載器。
  6. 如果上述加載過程全部失敗,拋出 ClassNotFound 異常。

????????Tomcat 的類加載器打破了雙親委派機制,沒有一上來就直接委托給父加載器,而是先在本地目錄下加載,為了避免本地目錄下的類覆蓋 JRE 的核心類,先嘗試用 JVM 擴展類加載器 ExtClassLoader 去加載。

三、web應用隔離

? ? ? ? 先回答開頭提到的第一個問題,如果我們使用 JVM 的 AppClassLoader 來加載 web 應用,AppClassLoader 只能加載一個 Servlet,再加載第二個同名的 Servlet 時,會返回第一個加載的 Servlet,同名的只被加載一次。Tomcat的解決方案是自定義一個類加載器 WebAppClassLoader,并且給每個 web 應用創建一個類加載器。不同的類加載器加載的類被認為是不同的類,即使名稱相同,web 應用通過各自的類加載器來實現隔離。

? ? ? ? 在來看第二個問題,多個 web 應用之間需要共享類庫,并且不能重復加載相同的類。在雙親委派機制里,各個子加載器都可以通過父加載器去加載類,那么把需要共享的類放到父加載器的加載路徑下不就行了嗎,應用程序也是通過這種方式共享 JRE 核心類。因此 Tomcat 的設計者又加了一個類加載器 SharedClassLoader,作為 WebAppClassLoader 的父加載器,專門來加載web 應用之間共享的類。如果 WebAppClassLoader 自己沒有加載到某個類,就會委托父加載器SharedClassLoader 去加載這個類,SharedClassLoader 會在指定目錄下加載共享類,之后返回給 WebAppClassLoader,這樣共享的問題就結局了。

? ? ? ? 第三個問題,如何隔離 Tomcat 本身的類和 web 應用的類?要共享可以通過父子關系,要隔離那就需要兄弟關系了。兄弟關系就是指兩個類加載器是平行的,他們可能擁有同一個父加載器,但是兩個兄弟類加載器加載的類是隔離的。因此 Tomcat 又設計了一個類加載器CatalinaClassLoader,專門來加載 Tomcat 自身的類,這樣設計有個問題,那 Tomcat 和 web 需要共享一些類時怎么辦呢?

????????還是再增加一個 CommonClassLoader,作為 CatalinaClassLoader 和 SharedClassLoader 的父加載器。CommonClassLoader 能加載的類都可以被 CatalinaClassLoader 和 SharedClassLoader 使用,而 CatalinaClassLoader 和 SharedClassLoader 能加載的類則與對方相互隔離。WebAppClassLoader 可以使用 SharedClassLoader 加載到的類,但各個 WebAppClassLoader 實例之間相互隔離。

? ? ? ? 3.1 Spring 加載問題

? ? ? ? 在 JVM 的實現中有一條規則,如果一個類由類加載器 A 加載,那么這個類的依賴類也由相同的類加載器完成。Spring 作為一個 bean 工廠,需要創建業務實體類,并且在創業業務類之前只要創建依賴類。

? ? ? ? 前面提到,web 應用之間共享的 JAR 包可以交給 SharedClassLoader 來加載,從而避免了重復,Spring 作為共享的三方 JAR 包,它自己是由?SharedClassLoader 加載的,但是Spring又要去加載業務類,但是業務類不在?SharedClassLoader 對應的目錄下,那該怎么辦呢?

? ? ? ? Tomcat 使用了線程上下文加載器,它其實是一種類加載傳遞機制。這個類加載器保存在線程私有數據里,只要是同一個線程,一旦設置了線程上下文加載器,在線程后續執行過程中,就能把這個加載器取出來用。

????????Tomcat 為每個 Web 應用創建一個 WebAppClassLoader 類加載器,并在啟動 Web 應用的線程里設置線程上下文加載器,這樣 Spring 在啟動時就將線程上下文加載器取出來,用來加載 Bean。這樣就完成了?SharedClassLoader 創建的 Spring 可以創建?WebAppClassLoader 下的業務類,是不是設計的很精妙呢?

? ? ? ? 好了本期內容就介紹到這里。

往期經典推薦:

Tomcat架構究竟是什么?靈魂原來在這里-CSDN博客

你真的了解Tomcat一鍵啟停嗎?-CSDN博客

你所不知的Tomcat網絡通信的玄機-CSDN博客

決勝高并發戰場:Redis并發訪問控制與實戰解析-CSDN博客

TiDB內核解密:揭秘其底層KV存儲引擎如何玩轉鍵值對-CSDN博客

????????

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/713780.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/713780.shtml
英文地址,請注明出處:http://en.pswp.cn/news/713780.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C++數據結構與算法——二叉樹的屬性

C第二階段——數據結構和算法&#xff0c;之前學過一點點數據結構&#xff0c;當時是基于Python來學習的&#xff0c;現在基于C查漏補缺&#xff0c;尤其是樹的部分。這一部分計劃一個月&#xff0c;主要利用代碼隨想錄來學習&#xff0c;刷題使用力扣網站&#xff0c;不定時更…

AGI概念與實現

AGI AGI&#xff08;Artificial General Intelligence&#xff09;&#xff0c;中文名為“通用人工智能”或“強人工智能”&#xff0c;是指通過機器學習和數據分析等技術&#xff0c;使計算機具有類似于人類的認知和學習能力的技術. 多模態的大模型 &#xff08;Multimodal…

詳細介紹如何用windows自帶Hyper-V安裝虛擬機(windows11和ubuntu22)

通過系統自帶的hyper-v安裝windows11&#xff0c;舒服又愜意&#xff0c;相比用第三方虛擬機軟件速度快很多。 硬件準備 準備 系統需要符合能安裝 Hyper-V 的最低要求windows版本含Hyper-V的功能 電腦空間 電腦要有足夠的空間來安裝你這個虛擬機。根據自己的磁盤容量情況來規…

2673. 使二叉樹所有路徑值相等的最小代價

給你一個整數 n 表示一棵 滿二叉樹 里面節點的數目&#xff0c;節點編號從 1 到 n 。根節點編號為 1 &#xff0c;樹中每個非葉子節點 i 都有兩個孩子&#xff0c;分別是左孩子 2 * i 和右孩子 2 * i 1 。 樹中每個節點都有一個值&#xff0c;用下標從 0 開始、長度為 n 的整…

CloudCanal x Hive 構建高效的實時數倉

簡述 CloudCanal 最近對于全周期數據流動進行了初步探索&#xff0c;打通了Hive 目標端的實時同步&#xff0c;為實時數倉的構建提供了支持&#xff0c;這篇文章簡要做下分享。 基于臨時表的增量合并方式基于 HDFS 文件寫入方式臨時表統一 Schema任務級的臨時表 基于臨時表的…

【Linux實踐室】Linux初體驗

&#x1f308;個人主頁&#xff1a;聆風吟 &#x1f525;系列專欄&#xff1a;Linux實踐室、網絡奇遇記 &#x1f516;少年有夢不應止于心動&#xff0c;更要付諸行動。 文章目錄 一. ??任務描述二. ??相關知識2.1 &#x1f514;Linux 目錄結構介紹2.2 &#x1f514;Linux …

WebFlux相關問題及答案(2024)

1、什么是Spring WebFlux&#xff1f; Spring WebFlux 是 Spring Framework 5.0 中引入的一個全新的反應式框架&#xff0c;用于構建異步、非阻塞且事件驅動的服務。它允許開發者使用響應式編程模型來處理并發性很高的操作&#xff0c;而無需擔心傳統的多線程環境中的復雜性。…

poi工具讀寫excel操作學習總結

寫在前面的話 POI作為比較早期的Excel處理工具&#xff0c;其使用較為成熟且廣泛。EasyExcel相較之下&#xff0c;則是相對較新的工具&#xff0c;其卻有著比POI更為優越的一些特性&#xff0c;如更加簡單的API接口和更加優秀的性能。 性能對比&#xff1a;在數據量較小的情況下…

mybatis mysql insert 主鍵id為空

錯誤示范 java代碼設置了param參數&#xff0c;但是sql 字段沒有帶上參數&#xff0c;例如 void insertV2(Param("historyDO") HistoryDO historyDO); <insert id"insertDuplicate" parameterType"com.test.entity.HistoryDO"keyProperty&…

MySQL:一行記錄如何

1、表空間文件結構 表空間由段「segment」、區「extent」、頁「page」、行「row」組成&#xff0c;InnoDB存儲引擎的邏輯存儲結構大致如下圖&#xff1a; 行 數據庫表中的記錄都是按「行」進行存放的&#xff0c;每行記錄根據不同的行格式&#xff0c;有不同的存儲結構。 頁…

hippy 調試demo運行聯調-mac環境準備篇

適用對于終端編譯環境不熟悉的人看&#xff0c;僅mac端 hippy 調試文檔官網地址 前提&#xff1a;請使用node16 聯調預覽效果圖&#xff1a; 編譯iOS Demo環境準備 未跑通&#xff0c;待補充 編譯Android Demo環境準備 1、正常安裝Android Studio 2、下載Android NDK&a…

Windows系統誤刪文件恢復

最近很多用戶反饋誤刪文件的場景比較多.下面華仔將講解數據恢復的原理和過程.以及一些注意事項。 建議的數據恢復軟件 1.EaseUS Data Recovery Wizard(易我數據恢復)需要斷網使用 2.Wondershare Recoverit(萬興數據恢復)&#xff0c; Windows系統刪除文件原理&#xff1a;如果是…

Android ShellUtils手機管理器

1. Android ShellUtils手機管理器 Android Shell工具類&#xff0c;可用于檢查系統root權限&#xff0c;并在shell或root用戶下執行shell命令。如&#xff1a; checkRootPermission() 檢查root權限 。execCommand(String[] commands, boolean isRoot, boolean isNeedResultMsg)…

HTTPS是什么,詳解它的加密過程

目錄 1.前言 2.兩種加密解密方式 2.1對稱加密 2.2非對稱加密 3.HTTPS的加密過程 3.1針對明文的對稱加密 3.2針對密鑰的非對稱加密 3.3證書的作用 1.前言 我們知道HTTP協議是超文本傳輸協議,它被廣泛的應用在客戶端服務器上,用來傳輸文字,圖片,視頻,js,html等.但是這種傳…

java數據結構與算法刷題-----LeetCode572. 另一棵樹的子樹(經典題,樹字符串化KMP)

java數據結構與算法刷題目錄&#xff08;劍指Offer、LeetCode、ACM&#xff09;-----主目錄-----持續更新(進不去說明我沒寫完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目錄 1. 暴力求解&#xff0c;深度優先2. KMP算法進行串匹配 1. 暴力求…

WinForm、Wpf自動升級 AutoUpdater.NET

Github AutoUpdater.NET 目錄 一、IIS部署 更新站點 二、創建Winform 一、IIS部署 更新站點 IIS默認站點目錄下創建 目錄 Downloads、Updates Updates目錄創建文件 UpdateLog.html、AutoUpdaterStarter.xml UpdateLog.html&#xff1a; <html><body><h1…

從零開始手寫RPC框架(2)——Netty入門

學習前需要掌握基本的java網絡編程&#xff0c;可參考這篇博客 目錄 Netty 簡介Netty 使用 kryo 序列化傳輸對象案例客戶端代碼服務端代碼編碼器 Netty 簡介 是什么&#xff1f; Netty 是一個基于 NIO (Non-blocking I/O&#xff0c;非阻塞I/O)的 client-server(客戶端服務器…

mysql學習--binlog與gtid主從同步

基礎環境 基于centOS7-MySQL8.0.35版本 我們先準備一臺主服務器兩臺從服務器來實現我們主從同步的訴求 Master&#xff1a;192.168.75.142 slave1:192.168.75.143 slave&#xff1a;192.168.75.145 binlog主從同步 主庫配置 #我們需要在主從庫中都需要添加server_id&am…

大龍談智能內容開通視頻號啦

大家好&#xff0c;大龍談只能內容開通視頻號了&#xff0c;歡迎大家掃碼關注&#xff1a;

RISC-V特權架構 - 中斷與異常概述

RISC-V特權架構 - 中斷與異常概述 1 中斷概述2 異常概述3 廣義上的異常3.1 同步異常3.2 異步異常3.3 常見同步異常和異步異常 本文屬于《 RISC-V指令集基礎系列教程》之一&#xff0c;歡迎查看其它文章。 1 中斷概述 中斷&#xff08;Interrupt&#xff09;機制&#xff0c;即…