最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

Tomcat 為什么要破壞 Java 雙親委派機制?

2023-08-18 23:02 作者:一起學(xué)chatGPT一起學(xué)ai  | 我要投稿

大家好,我是鋒哥!!


我們分為4個部分來探討:

  1. 什么是類加載機制?
  2. 什么是雙親委任模型?
  3. 如何破壞雙親委任模型?
  4. Tomcat 的類加載器是怎么設(shè)計的?

我想,在研究tomcat 類加載之前,我們復(fù)習一下或者說鞏固一下java 默認的類加載器。樓主以前對類加載也是懵懵懂懂,借此機會,也好好復(fù)習一下。

樓主翻開了神書《深入理解Java虛擬機》第二版,p227, 關(guān)于類加載器的部分。請看:

1. 什么是類加載機制?

代碼編譯的結(jié)果從本地機器碼轉(zhuǎn)變成字節(jié)碼,是存儲格式的一小步,卻是編程語言發(fā)展的一大步。

Java虛擬機把描述類的數(shù)據(jù)從Class文件加載進內(nèi)存,并對數(shù)據(jù)進行校驗,轉(zhuǎn)換解析和初始化,最終形成可以唄虛擬機直接使用的Java類型,這就是虛擬機的類加載機制。

虛擬機設(shè)計團隊把類加載階段中的“通過一個類的全限定名來獲取描述此類的二進制字節(jié)流”這個動作放到Java虛擬機外部去實現(xiàn),以便讓應(yīng)用程序自己決定如何去獲取所需要的類。實現(xiàn)這動作的代碼模塊成為“類加載器”。

類與類加載器的關(guān)系

類加載器雖然只用于實現(xiàn)類的加載動作,但它在Java程序中起到的作用卻遠遠不限于類加載階段。對于任意一個類,都需要由加載他的類加載器和這個類本身一同確立其在Java虛擬機中的唯一性,每一個類加載器,都擁有一個獨立的類命名空間。

這句話可以表達的更通俗一些:比較兩個類是否“相等”,只有在這兩個類是由同一個類加載器加載的前提下才有意義,否則,即使這兩個類來自同一個Class文件,被同一個虛擬機加載,只要加載他們的類加載器不同,那這個兩個類就必定不相等。

2. 什么是雙親委任模型

從Java虛擬機的角度來說,只存在兩種不同類加載器:一種是啟動類加載器(Bootstrap ClassLoader),這個類加載器使用C++語言實現(xiàn)(只限HotSpot),是虛擬機自身的一部分;另一種就是所有其他的類加載器,這些類加載器都由Java語言實現(xiàn),獨立于虛擬機外部,并且全都繼承自抽象類java.lang.ClassLoader.

從Java開發(fā)人員的角度來看,類加載還可以劃分的更細致一些,絕大部分Java程序員都會使用以下3種系統(tǒng)提供的類加載器:

  • 啟動類加載器(Bootstrap ClassLoader):這個類加載器復(fù)雜將存放在 JAVA_HOME/lib 目錄中的,或者被-Xbootclasspath 參數(shù)所指定的路徑種的,并且是虛擬機識別的(僅按照文件名識別,如rt.jar,名字不符合的類庫即使放在lib目錄下也不會重載)。
  • 擴展類加載器(Extension ClassLoader):這個類加載器由sun.misc.Launcher$ExtClassLoader實現(xiàn),它負責夾雜JAVA_HOME/lib/ext 目錄下的,或者被java.ext.dirs 系統(tǒng)變量所指定的路徑種的所有類庫。開發(fā)者可以直接使用擴展類加載器。
  • 應(yīng)用程序類加載器(Application ClassLoader):這個類加載器由sun.misc.Launcher$AppClassLoader 實現(xiàn)。由于這個類加載器是ClassLoader 種的getSystemClassLoader方法的返回值,所以也成為系統(tǒng)類加載器。它負責加載用戶類路徑(ClassPath)上所指定的類庫。開發(fā)者可以直接使用這個類加載器,如果應(yīng)用中沒有定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。

這些類加載器之間的關(guān)系一般如下圖所示:


圖中各個類加載器之間的關(guān)系成為 類加載器的雙親委派模型(Parents Dlegation Mode)。雙親委派模型要求除了頂層的啟動類加載器之外,其余的類加載器都應(yīng)當由自己的父類加載器加載,這里類加載器之間的父子關(guān)系一般不會以繼承的關(guān)系來實現(xiàn),而是都使用組合關(guān)系來復(fù)用父加載器的代碼。

類加載器的雙親委派模型在JDK1.2 期間被引入并被廣泛應(yīng)用于之后的所有Java程序中,但他并不是個強制性的約束模型,而是Java設(shè)計者推薦給開發(fā)者的一種類加載器實現(xiàn)方式。

雙親委派模型的工作過程是:如果一個類加載器收到了類加載的請求,他首先不會自己去嘗試加載這個類,而是把這個請求委派父類加載器去完成。每一個層次的類加載器都是如此,因此所有的加載請求最終都應(yīng)該傳送到頂層的啟動類加載器中,只有當父加載器反饋自己無法完成這個請求(他的搜索范圍中沒有找到所需的類)時,子加載器才會嘗試自己去加載。

為什么要這么做呢?

如果沒有使用雙親委派模型,由各個類加載器自行加載的話,如果用戶自己編寫了一個稱為java.lang.Object的類,并放在程序的ClassPath中,那系統(tǒng)將會出現(xiàn)多個不同的Object類, Java類型體系中最基礎(chǔ)的行為就無法保證。應(yīng)用程序也將會變得一片混亂。

雙親委任模型時如何實現(xiàn)的?

非常簡單:所有的代碼都在java.lang.ClassLoader中的loadClass方法之中,代碼如下:


邏輯清晰易懂:先檢查是否已經(jīng)被加載過,若沒有加載則調(diào)用父加載器的loadClass方法, 如父加載器為空則默認使用啟動類加載器作為父加載器。如果父類加載失敗,拋出ClassNotFoundException 異常后,再調(diào)用自己的findClass方法進行加載。

3. 如何破壞雙親委任模型?

剛剛我們說過,雙親委任模型不是一個強制性的約束模型,而是一個建議型的類加載器實現(xiàn)方式。在Java的世界中大部分的類加載器都遵循者模型,但也有例外,到目前為止,雙親委派模型有過3次大規(guī)模的“被破壞”的情況。

第一次: 在雙親委派模型出現(xiàn)之前-----即JDK1.2發(fā)布之前。

第二次: 是這個模型自身的缺陷導(dǎo)致的。我們說,雙親委派模型很好的解決了各個類加載器的基礎(chǔ)類的統(tǒng)一問題(越基礎(chǔ)的類由越上層的加載器進行加載),基礎(chǔ)類之所以稱為“基礎(chǔ)”,是因為它們總是作為被用戶代碼調(diào)用的API, 但沒有絕對,如果基礎(chǔ)類調(diào)用會用戶的代碼怎么辦呢?

這不是沒有可能的。一個典型的例子就是JNDI服務(wù),JNDI現(xiàn)在已經(jīng)是Java的標準服務(wù),它的代碼由啟動類加載器去加載(在JDK1.3時就放進去的rt.jar),但它需要調(diào)用由獨立廠商實現(xiàn)并部署在應(yīng)用程序的ClassPath下的JNDI接口提供者(SPI, Service Provider Interface)的代碼,但啟動類加載器不可能“認識“這些代碼啊。因為這些類不在rt.jar中,但是啟動類加載器又需要加載。怎么辦呢?

為了解決這個問題,Java設(shè)計團隊只好引入了一個不太優(yōu)雅的設(shè)計:線程上下文類加載器(Thread Context ClassLoader)。這個類加載器可以通過java.lang.Thread類的setContextClassLoader方法進行設(shè)置。如果創(chuàng)建線程時還未設(shè)置,它將會從父線程中繼承一個,如果在應(yīng)用程序的全局范圍內(nèi)都沒有設(shè)置過多的話,那這個類加載器默認即使應(yīng)用程序類加載器。

嘿嘿,有了線程上下文加載器,JNDI服務(wù)使用這個線程上下文加載器去加載所需要的SPI代碼,也就是父類加載器請求子類加載器去完成類加載的動作,這種行為實際上就是打通了雙親委派模型的層次結(jié)構(gòu)來逆向使用類加載器,實際上已經(jīng)違背了雙親委派模型的一般性原則。但這無可奈何,Java中所有涉及SPI的加載動作基本勝都采用這種方式。例如JNDI,JDBC,JCE,JAXB,JBI等。

第三次: 為了實現(xiàn)熱插拔,熱部署,模塊化,意思是添加一個功能或減去一個功能不用重啟,只需要把這模塊連同類加載器一起換掉就實現(xiàn)了代碼的熱替換。

書中還說到:

Java 程序中基本有一個共識:OSGI對類加載器的使用時值得學(xué)習的,弄懂了OSGI的實現(xiàn),就可以算是掌握了類加載器的精髓。

牛逼?。。。?/p>

現(xiàn)在,我們已經(jīng)基本明白了Java默認的類加載的作用了原理,也知道雙親委派模型。說了這么多,差點把我們的tomcat給忘了,我們的題目是Tomcat 加載器為何違背雙親委派模型?下面就好好說說我們的tomcat的類加載器。

4. Tomcat 的類加載器是怎么設(shè)計的?

首先,我們來問個問題:

Tomcat 如果使用默認的類加載機制行不行?

我們思考一下:Tomcat是個web容器, 那么它要解決什么問題:

  • 一個web容器可能需要部署兩個應(yīng)用程序,不同的應(yīng)用程序可能會依賴同一個第三方類庫的不同版本,不能要求同一個類庫在同一個服務(wù)器只有一份,因此要保證每個應(yīng)用程序的類庫都是獨立的,保證相互隔離。
  • 部署在同一個web容器中相同的類庫相同的版本可以共享。否則,如果服務(wù)器有10個應(yīng)用程序,那么要有10份相同的類庫加載進虛擬機,這是扯淡的。
  • web容器也有自己依賴的類庫,不能于應(yīng)用程序的類庫混淆?;诎踩紤],應(yīng)該讓容器的類庫和程序的類庫隔離開來。
  • web容器要支持jsp的修改,我們知道,jsp 文件最終也是要編譯成class文件才能在虛擬機中運行,但程序運行后修改jsp已經(jīng)是司空見慣的事情,否則要你何用?所以,web容器需要支持 jsp 修改后不用重啟。

再看看我們的問題:Tomcat 如果使用默認的類加載機制行不行?

答案是不行的。為什么?我們看,第一個問題,如果使用默認的類加載器機制,那么是無法加載兩個相同類庫的不同版本的,默認的累加器是不管你是什么版本的,只在乎你的全限定類名,并且只有一份。第二個問題,默認的類加載器是能夠?qū)崿F(xiàn)的,因為他的職責就是保證唯一性。第三個問題和第一個問題一樣。

我們再看第四個問題,我們想我們要怎么實現(xiàn)jsp文件的熱修改(樓主起的名字),jsp 文件其實也就是class文件,那么如果修改了,但類名還是一樣,類加載器會直接取方法區(qū)中已經(jīng)存在的,修改后的jsp是不會重新加載的。那么怎么辦呢?

我們可以直接卸載掉這jsp文件的類加載器,所以你應(yīng)該想到了,每個jsp文件對應(yīng)一個唯一的類加載器,當一個jsp文件修改了,就直接卸載這個jsp類加載器。重新創(chuàng)建類加載器,重新加載jsp文件。

Tomcat 如何實現(xiàn)自己獨特的類加載機制?

所以,Tomcat 是怎么實現(xiàn)的呢?牛逼的Tomcat團隊已經(jīng)設(shè)計好了。我們看看他們的設(shè)計圖:


我們看到,前面3個類加載和默認的一致,CommonClassLoader、CatalinaClassLoader、SharedClassLoader和WebappClassLoader則是Tomcat自己定義的類加載器,它們分別加載/common/*、/server/*、/shared/*(在tomcat 6之后已經(jīng)合并到根目錄下的lib目錄下)和/WebApp/WEB-INF/*中的Java類庫。

其中WebApp類加載器和Jsp類加載器通常會存在多個實例,每一個Web應(yīng)用程序?qū)?yīng)一個WebApp類加載器,每一個JSP文件對應(yīng)一個Jsp類加載器。

  • commonLoader:Tomcat最基本的類加載器,加載路徑中的class可以被Tomcat容器本身以及各個Webapp訪問;
  • catalinaLoader:Tomcat容器私有的類加載器,加載路徑中的class對于Webapp不可見;
  • sharedLoader:各個Webapp共享的類加載器,加載路徑中的class對于所有Webapp可見,但是對于Tomcat容器不可見;
  • WebappClassLoader:各個Webapp私有的類加載器,加載路徑中的class只對當前Webapp可見;

從圖中的委派關(guān)系中可以看出:

CommonClassLoader能加載的類都可以被Catalina ClassLoader和SharedClassLoader使用,從而實現(xiàn)了公有類庫的共用,而CatalinaClassLoader和Shared ClassLoader自己能加載的類則與對方相互隔離。

WebAppClassLoader可以使用SharedClassLoader加載到的類,但各個WebAppClassLoader實例之間相互隔離。

而JasperLoader的加載范圍僅僅是這個JSP文件所編譯出來的那一個.Class文件,它出現(xiàn)的目的就是為了被丟棄:當Web容器檢測到JSP文件被修改時,會替換掉目前的JasperLoader的實例,并通過再建立一個新的Jsp類加載器來實現(xiàn)JSP文件的HotSwap功能。

好了,至此,我們已經(jīng)知道了tomcat為什么要這么設(shè)計,以及是如何設(shè)計的,那么,tomcat 違背了java 推薦的雙親委派模型了嗎?答案是:違背了。我們前面說過:

雙親委派模型要求除了頂層的啟動類加載器之外,其余的類加載器都應(yīng)當由自己的父類加載器加載。

很顯然,tomcat 不是這樣實現(xiàn),tomcat 為了實現(xiàn)隔離性,沒有遵守這個約定,每個webappClassLoader加載自己的目錄下的class文件,不會傳遞給父類加載器。

我們擴展出一個問題:如果tomcat 的 Common ClassLoader 想加載 WebApp ClassLoader 中的類,該怎么辦?

看了前面的關(guān)于破壞雙親委派模型的內(nèi)容,我們心里有數(shù)了,我們可以使用線程上下文類加載器實現(xiàn),使用線程上下文加載器,可以讓父類加載器請求子類加載器去完成類加載的動作。牛逼吧。

總結(jié)

好了,終于,我們明白了Tomcat 為何違背雙親委派模型,也知道了tomcat的類加載器是如何設(shè)計的。順便復(fù)習了一下 Java 默認的類加載器機制,也知道了如何破壞Java的類加載機制。這一次收獲不小哦?。?!嘿嘿。


Tomcat 為什么要破壞 Java 雙親委派機制?的評論 (共 條)

分享到微博請遵守國家法律
平定县| 吉水县| 奉节县| 寿宁县| 资阳市| 白水县| 阿图什市| 游戏| 鄂伦春自治旗| 阿拉善左旗| 清涧县| 中山市| 吉林省| 通城县| 洛南县| 昔阳县| 五寨县| 宜昌市| 团风县| 内丘县| 涿鹿县| 繁峙县| 乌兰察布市| 东乌珠穆沁旗| 永仁县| 金昌市| 西吉县| 泸州市| 湖州市| 德兴市| 吉林市| 东乡| 佛山市| 靖安县| 古田县| 黄浦区| 汾阳市| 仪陇县| 建平县| 屏东县| 宁波市|