<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 6942字,需瀏覽 14分鐘

           ·

          2023-08-29 13:21



          來源:www.jianshu.com/p /abf6fd4531e7

          • 1.什么是類加載機制?
            • 類與類加載器的關(guān)系
          • 2.什么是雙親委任模型
            • 為什么要這么做呢?
            • 雙親委任模型時如何實現(xiàn)的?
          • 3.如何破壞雙親委任模型?
          • 4.Tomcat 的類加載器是怎么設(shè)計的?
            • Tomcat 如何實現(xiàn)自己獨特的類加載機制?
          • 總結(jié)
          圖片

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

          樓主翻開了神書《深入理解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. 什么是雙親委任模型

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

          2.從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),它負責(zé)夾雜JAVA_HOME/lib/ext 目錄下的,或者被java.ext.dirs 系統(tǒng)變量所指定的路徑種的所有類庫。開發(fā)者可以直接使用擴展類加載器。
          • 應(yīng)用程序類加載器(Application ClassLoader): 這個類加載器由sun.misc.Launcher$AppClassLoader實現(xiàn)。由于這個類加載器是ClassLoader 種的getSystemClassLoader方法的返回值,所以也成為系統(tǒng)類加載器。它負責(zé)加載用戶類路徑(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é)習(xí)的,弄懂了OSGI的實現(xiàn),就可以算是掌握了類加載器的精髓。

          牛逼啊!!!

          現(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)的,因為他的職責(zé)就是保證唯一性。第三個問題和第一個問題一樣。我們再看第四個問題,我們想我們要怎么實現(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ù)習(xí)了一下 Java 默認的類加載器機制,也知道了如何破壞Java的類加載機制。這一次收獲不小哦!!!嘿嘿。

              

          程序汪資料鏈接

          程序汪接的7個私活都在這里,經(jīng)驗整理

          Java項目分享  最新整理全集,找項目不累啦 07版

          堪稱神級的Spring Boot手冊,從基礎(chǔ)入門到實戰(zhàn)進階

          臥槽!字節(jié)跳動《算法中文手冊》火了,完整版 PDF 開放下載!

          臥槽!阿里大佬總結(jié)的《圖解Java》火了,完整版PDF開放下載!

          字節(jié)跳動總結(jié)的設(shè)計模式 PDF 火了,完整版開放下載!

          歡迎添加程序汪個人微信 itwang007  進粉絲群或圍觀朋友圈

          瀏覽 1069
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天爽天天狠 | 免费av毛片 | 内射啪啪网站 | av天堂男人 | 日韩高清免费AV一区二区三区 |