Tomcat類加載器破壞雙親委派

www.cnblogs.com/fanguangdex…

轉載:blog.csdn.net/qq_38182963…java

www.cnblogs.com/aspirant/p/…web

www.cnblogs.com/xing901022/…面試

雙親委派模式的破壞

第一次破壞:向前兼容

雙親委派模型的第一次「被破壞」其實發生在雙親委派模型出現以前–即JDK1.2發佈以前。因爲雙親委派模型是在JDK1.2以後才被引入的,而類加載器和抽象類java.lang.ClassLoader則是JDK1.0時候就已經存在,面對已經存在 的用戶自定義類加載器的實現代碼,Java設計者引入雙親委派模型時不得不作出一些妥協。爲了向前兼容,JDK1.2以後的java.lang.ClassLoader添加了一個新的proceted方法findClass(),在此以前,用戶去繼承java.lang.ClassLoader的惟一目的就是重寫loadClass()方法,由於虛擬在進行類加載的時候會調用加載器的私有方法loadClassInternal(),而這個方法的惟一邏輯就是去調用本身的loadClass()。JDK1.2以後已再也不提倡用戶再去覆蓋loadClass()方法,應當把本身的類加載邏輯寫到findClass()方法中,在loadClass()方法的邏輯裏,若是父類加載器加載失敗,則會調用本身的findClass()方法來完成加載,這樣就能夠保證新寫出來的類加載器是符合雙親委派模型的。 緩存

第二次破壞:加載SPI接口實現類

雙親委派模型的第二次「被破壞」是這個模型自身的缺陷所致使的,雙親委派模型很好地解決了各個類加載器的基礎類統一問題(越基礎的類由越上層的加載器進行加載),基礎類之因此被稱爲「基礎」,是由於它們老是做爲被調用代碼調用的API。可是,若是基礎類又要調用用戶的代碼,那該怎麼辦呢。
這並不是是不可能的事情,一個典型的例子即是JNDI服務,它的代碼由啓動類加載器去加載(在JDK1.3時放進rt.jar),但JNDI的目的就是對資源進行集中管理和查找,它須要調用獨立廠商實現部部署在應用程序的classpath下的JNDI接口提供者(SPI, Service Provider Interface)的代碼,但啓動類加載器不可能「認識」之些代碼,該怎麼辦?
爲了解決這個困境,Java設計團隊只好引入了一個不太優雅的設計:線程上下文件類加載器(Thread Context ClassLoader)。這個類加載器能夠經過java.lang.Thread類的setContextClassLoader()方法進行設置,若是建立線程時還未設置,它將會從父線程中繼承一個;若是在應用程序的全局範圍內都沒有設置過,那麼這個類加載器默認就是應用程序類加載器。了有線程上下文類加載器,JNDI服務使用這個線程上下文類加載器去加載所須要的SPI代碼,也就是父類加載器請求子類加載器去完成類加載動做,這種行爲實際上就是打通了雙親委派模型的層次結構來逆向使用類加載器,已經違背了雙親委派模型,但這也是迫不得已的事情。Java中全部涉及SPI的加載動做基本上都採用這種方式,例如JNDI,JDBC,JCE,JAXB和JBI等。tomcat

第三次破壞:熱部署

雙親委派模型的第三次「被破壞」是因爲用戶對程序的動態性的追求致使的。爲了實現熱插拔,熱部署,模塊化,意思是添加一個功能或減去一個功能不用重啓,只須要把這模塊連同類加載器一塊兒換掉就實現了代碼的熱替換。例如OSGi的出現。在OSGi環境下,類加載器再也不是雙親委派模型中的樹狀結構,而是進一步發展爲網狀結構。安全

Java 程序中基本有一個共識:OSGI對類加載器的使用時值得學習的,弄懂了OSGI的實現,就能夠算是掌握了類加載器的精髓。服務器

Tomcat類加載器

拋出問題

一、既然 Tomcat 不遵循雙親委派機制,那麼若是我本身定義一個惡意的HashMap,會不會有風險呢?(阿里的面試官問)數據結構

答: 顯然不會有風險,若是有,Tomcat都運行這麼多年了,那羣Tomcat大神能不改進嗎? tomcat不遵循雙親委派機制,只是自定義的classLoader順序不一樣,但頂層仍是相同的,app

仍是要去頂層請求classloader.

二、咱們思考一下:Tomcat是個web容器, 那麼它要解決什麼問題:
1. 一個web容器可能須要部署兩個應用程序,不一樣的應用程序可能會依賴同一個第三方類庫的不一樣版本,不能要求同一個類庫在同一個服務器只有一份,所以要保證每一個應用程序的類庫都是獨立的,保證相互隔離。
2. 部署在同一個web容器中相同的類庫相同的版本能夠共享。不然,若是服務器有10個應用程序,那麼要有10份相同的類庫加載進虛擬機,這是扯淡的。
3. web容器也有本身依賴的類庫,不能於應用程序的類庫混淆。基於安全考慮,應該讓容器的類庫和程序的類庫隔離開來。
4. web容器要支持jsp的修改,咱們知道,jsp 文件最終也是要編譯成class文件才能在虛擬機中運行,但程序運行後修改jsp已是司空見慣的事情,不然要你何用? 因此,web容器須要支持 jsp 修改後不用重啓。

再看看咱們的問題:Tomcat 若是使用默認的類加載機制行不行?
答案是不行的。爲何?咱們看,第一個問題,若是使用默認的類加載器機制,那麼是沒法加載兩個相同類庫的不一樣版本的,默認的類加載器是無論你是什麼版本的,只在意你的全限定類名,而且只有一份。第二個問題,默認的類加載器是可以實現的,由於他的職責就是保證惟一性。第三個問題和第一個問題同樣。咱們再看第四個問題,咱們想咱們要怎麼實現jsp文件的熱修改(樓主起的名字),jsp 文件其實也就是class文件,那麼若是修改了,但類名仍是同樣,類加載器會直接取方法區中已經存在的,修改後的jsp是不會從新加載的。那麼怎麼辦呢?咱們能夠直接卸載掉這jsp文件的類加載器,因此你應該想到了,每一個jsp文件對應一個惟一的類加載器,當一個jsp文件修改了,就直接卸載這個jsp類加載器。從新建立類加載器,從新加載jsp文件。

Tomcat 如何實現本身獨特的類加載機制?

因此,Tomcat 是怎麼實現的呢?牛逼的Tomcat團隊已經設計好了。咱們看看他們的設計圖:

咱們看到,前面3個類加載和默認的一致,CommonClassLoader、CatalinaClassLoader、SharedClassLoader和WebappClassLoader則是Tomcat本身定義的類加載器,它們分別加載/common/*/server/*/shared/*(在tomcat 6以後已經合併到根目錄下的lib目錄下)和/WebApp/WEB-INF/*中的Java類庫。其中WebApp類加載器和Jsp類加載器一般會存在多個實例,每個Web應用程序對應一個WebApp類加載器,每個JSP文件對應一個Jsp類加載器。

  • commonLoader:Tomcat最基本的類加載器,加載路徑中的class能夠被Tomcat容器自己以及各個Webapp(web應用)訪問;
  • catalinaLoader:Tomcat容器私有的類加載器,加載路徑中的class對於Webapp不可見;
  • sharedLoader:各個Webapp共享的類加載器,加載路徑中的class對於全部Webapp可見,可是對於Tomcat容器不可見;
  • WebappClassLoader:各個Webapp私有的類加載器,加載路徑中的class只對當前Webapp可見;

從圖中的委派關係中能夠看出:

CommonClassLoader能加載的類均可以被Catalina ClassLoader和SharedClassLoader使用,從而實現了公有類庫的共用,而CatalinaClassLoader和Shared ClassLoader本身能加載的類則與對方相互隔離。

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

而JasperLoader的加載範圍僅僅是這個JSP文件所編譯出來的那一個.Class文件,它出現的目的就是爲了被丟棄:當Web容器檢測到JSP文件被修改時,會替換掉目前的JasperLoader的實例,並經過再創建一個新的Jsp類加載器來實現JSP文件的HotSwap功能。

好了,至此,咱們已經知道了tomcat爲何要這麼設計,以及是如何設計的,那麼,tomcat 違背了java 推薦的雙親委派模型了嗎?答案是:違背了。 咱們前面說過:

雙親委派模型要求除了頂層的啓動類加載器以外,其他的類加載器都應當由本身的父類加載器加載。

很顯然,tomcat 不是這樣實現,tomcat 爲了實現隔離性,沒有遵照這個約定,每一個webappClassLoader加載本身的目錄下的class文件,不會傳遞給父類加載器。

咱們擴展出一個問題:若是tomcat 的 Common ClassLoader 想加載 WebApp ClassLoader 中的類,該怎麼辦?

看了前面的關於破壞雙親委派模型的內容,咱們內心有數了,咱們可使用線程上下文類加載器實現,使用線程上下文加載器,可讓父類加載器請求子類加載器去完成類加載的動做。

Tomcat類加載過程

tomcat的類加載機制是違反了雙親委託原則的,對於一些未加載的非基礎類(Object,String等),各個web應用本身的類加載器(WebAppClassLoader)會優先加載,加載不到時再交給commonClassLoader走雙親委託。具體的加載邏輯位於WebAppClassLoaderBase.loadClass()方法中,代碼篇幅長,這裏以文字描述加載一個類過程:

  1. 先在本地緩存中查找是否已經加載過該類(對於一些已經加載了的類,會被緩存在resourceEntries這個數據結構中),若是已經加載即返回,不然 繼續下一步。
  2. 讓系統類加載器(AppClassLoader)嘗試加載該類,主要是爲了防止一些基礎類會被web中的類覆蓋,若是加載到即返回,返回繼續。
  3. 前兩步均沒加載到目標類,那麼web應用的類加載器將自行加載,若是加載到則返回,不然繼續下一步。
  4. 最後仍是加載不到的話,則委託父類加載器(Common ClassLoader)去加載。

第3第4兩個步驟的順序已經違反了雙親委託機制,除了tomcat以外,JDBC,JNDI,Thread.currentThread().setContextClassLoader();等不少地方都同樣是違反了雙親委託。

相關文章
相關標籤/搜索