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

面試官:要不你今天來詳細(xì)講講雙親委派機制?
候選者:嗯,好的。
候選者:上次提到了:class文件是通過「類加載器」裝載至JVM中的
候選者:為了防止內(nèi)存中存在多份同樣的字節(jié)碼,使用了雙親委派機制(它不會自己去嘗試加載類,而是把請求委托給父加載器去完成,依次向上)
候選者:JDK 中的本地方法類一般由根加載器(Bootstrp loader)裝載,JDK 中內(nèi)部實現(xiàn)的擴展類一般由擴展加載器(ExtClassLoader )實現(xiàn)裝載,而程序中的類文件則由系統(tǒng)加載器(AppClassLoader )實現(xiàn)裝載。

候選者:這應(yīng)該很好理解吧?
面試官:雀食(確實)!
面試官:順著話題,我想問問,打破雙親委派機制是什么意思?
候選者:很好理解啊,意思就是:只要我加載類的時候,不是從APPClassLoader->Ext ClassLoader->BootStrap ClassLoader 這個順序找,那就算是打破了啊
候選者:因為加載class核心的方法在LoaderClass類的loadClass方法上(雙親委派機制的核心實現(xiàn))
候選者:那只要我自定義個ClassLoader,重寫loadClass方法(不依照往上開始尋找類加載器),那就算是打破雙親委派機制了。
面試官:這么簡單?
候選者:嗯,就是這么簡單
面試官:那你知道有哪個場景破壞了雙親委派機制嗎?
候選者:最明顯的就Tomcat啊
面試官:詳細(xì)說說?
候選者:在初學(xué)時部署項目,我們是把war包放到tomcat的webapp下,這意味著一個tomcat可以運行多個Web應(yīng)用程序(:
候選者:是吧?
面試官:嗯..
候選者:那假設(shè)我現(xiàn)在有兩個Web應(yīng)用程序,它們都有一個類,叫做User,并且它們的類全限定名都一樣,比如都是com.yyy.User。但是他們的具體實現(xiàn)是不一樣的
候選者:那么Tomcat是如何保證它們是不會沖突的呢?
候選者:答案就是,Tomcat給每個 Web 應(yīng)用創(chuàng)建一個類加載器實例(WebAppClassLoader),該加載器重寫了loadClass方法,優(yōu)先加載當(dāng)前應(yīng)用目錄下的類,如果當(dāng)前找不到了,才一層一層往上找(:
候選者:那這樣就做到了Web應(yīng)用層級的隔離

面試官:嗯,那你還知道Tomcat還有別的類加載器嗎?
候選者:嗯,知道的
候選者:并不是Web應(yīng)用程序下的所有依賴都需要隔離的,比如Redis就可以Web應(yīng)用程序之間共享(如果有需要的話),因為如果版本相同,沒必要每個Web應(yīng)用程序都獨自加載一份啊。
候選者:做法也很簡單,Tomcat就在WebAppClassLoader上加了個父類加載器(SharedClassLoader),如果WebAppClassLoader自身沒有加載到某個類,那就委托SharedClassLoader去加載。
候選者:(無非就是把需要應(yīng)用程序之間需要共享的類放到一個共享目錄下嘛)
面試官:嗯..
候選者:為了隔絕Web應(yīng)用程序與Tomcat本身的類,又有類加載器(CatalinaClassLoader)來裝載Tomcat本身的依賴
候選者:如果Tomcat本身的依賴和Web應(yīng)用還需要共享,那么還有類加載器(CommonClassLoader)來裝載進(jìn)而達(dá)到共享
候選者:各個類加載器的加載目錄可以到tomcat的catalina.properties配置文件上查看
候選者:我稍微畫下Tomcat的類加載結(jié)構(gòu)圖吧,不然有點抽象

面試官:嗯,還可以,我聽懂了,有點意思。
面試官:順便,我想問下,JDBC你不是知道嗎,聽說它也是破壞了雙親委派模型的,你怎么理解的。
候選者:Eumm,這個有沒有破壞,見仁見智吧。
候選者:JDBC定義了接口,具體實現(xiàn)類由各個廠商進(jìn)行實現(xiàn)嘛(比如MySQL)
候選者:類加載有個規(guī)則:如果一個類由類加載器A加載,那么這個類的依賴類也是由「相同的類加載器」加載。
候選者:我們用JDBC的時候,是使用DriverManager進(jìn)而獲取Connection,DriverManager在java.sql包下,顯然是由BootStrap類加載器進(jìn)行裝載
候選者:當(dāng)我們使用DriverManager.getConnection()時,得到的一定是廠商實現(xiàn)的類。
候選者:但BootStrap ClassLoader會能加載到各個廠商實現(xiàn)的類嗎?
候選者:顯然不可以啊,這些實現(xiàn)類又沒在java包中,怎么可能加載得到呢
面試官:嗯..
候選者:DriverManager的解決方案就是,在DriverManager初始化的時候,得到「線程上下文加載器」
候選者:去獲取Connection的時候,是使用「線程上下文加載器」去加載Connection的,而這里的線程上下文加載器實際上還是App ClassLoader
候選者:所以在獲取Connection的時候,還是先找Ext ClassLoader和BootStrap ClassLoader,只不過這倆加載器肯定是加載不到的,最終會由App ClassLoader進(jìn)行加載

面試官:嗯..
候選者:那這種情況,有的人覺得破壞了雙親委派機制,因為本來明明應(yīng)該是由BootStrap ClassLoader進(jìn)行加載的,結(jié)果你來了一手「線程上下文加載器」,改掉了「類加載器」
候選者:有的人覺得沒破壞雙親委派機制,只是改成由「線程上下文加載器」進(jìn)行類加載,但還是遵守著:「依次往上找父類加載器進(jìn)行加載,都找不到時才由自身加載」。認(rèn)為”原則”上是沒變的。
面試官:那我了解了
本文總結(jié):
前置知識: JDK中默認(rèn)類加載器有三個:AppClassLoader、Ext ClassLoader、BootStrap ClassLoader。AppClassLoader的父加載器為Ext ClassLoader、Ext ClassLoader的父加載器為BootStrap ClassLoader。這里的父子關(guān)系并不是通過繼承實現(xiàn)的,而是組合。
什么是雙親委派機制: 加載器在加載過程中,先把類交由父類加載器進(jìn)行加載,父類加載器沒找到才由自身加載。
雙親委派機制目的: 為了防止內(nèi)存中存在多份同樣的字節(jié)碼(安全)
類加載規(guī)則: 如果一個類由類加載器A加載,那么這個類的依賴類也是由「相同的類加載器」加載。
如何打破雙親委派機制: 自定義ClassLoader,重寫loadClass方法(只要不依次往上交給父加載器進(jìn)行加載,就算是打破雙親委派機制)
打破雙親委派機制案例: Tomcat
為了Web應(yīng)用程序類之間隔離,為每個應(yīng)用程序創(chuàng)建WebAppClassLoader類加載器 為了Web應(yīng)用程序類之間共享,把ShareClassLoader作為WebAppClassLoader的父類加載器,如果WebAppClassLoader加載器找不到,則嘗試用ShareClassLoader進(jìn)行加載 為了Tomcat本身與Web應(yīng)用程序類隔離,用CatalinaClassLoader類加載器進(jìn)行隔離,CatalinaClassLoader加載Tomcat本身的類 為了Tomcat與Web應(yīng)用程序類共享,用CommonClassLoader作為CatalinaClassLoader和ShareClassLoader的父類加載器 ShareClassLoader、CatalinaClassLoader、CommonClassLoader的目錄可以在Tomcat的catalina.properties進(jìn)行配置 線程上下文加載器: 由于類加載的規(guī)則,很可能導(dǎo)致父加載器加載時依賴子加載器的類,導(dǎo)致無法加載成功(BootStrap ClassLoader無法加載第三方庫的類),所以存在「線程上下文加載器」來進(jìn)行加載。
