主流的Java Web服務器(也就是Web容器),如Tomcat淑趾、Jetty阳仔、WebLogic、WebSphere或其他筆者沒有列舉的服務器扣泊,都實現(xiàn)了自己定義的類加載器(一般都不止一個)近范。因為一個功能健全的Web容器嘶摊,要解決如下幾個問題:
1)部署在同一個Web容器上的兩個Web應用程序所使用的Java類庫可以實現(xiàn)相互隔離。這是最基本的需求评矩,兩個不同的應用程序可能會依賴同一個第三方類庫的不同版本叶堆,不能要求一個類庫在一個服務器中只有一份,服務器應當保證兩個應用程序的類庫可以互相獨立使用斥杜。
2)部署在同一個Web容器上的兩個Web應用程序所使用的Java類庫可以互相共享虱颗。這個需求也很常見,例如蔗喂,用戶可能有10個使用Spring組織的應用程序部署在同一臺服務器上忘渔,如果把10份Spring分別存放在各個應用程序的隔離目錄中,將會是很大的資源浪費——這主要倒不是浪費磁盤空間的問題弱恒,而是指類庫在使用時都要被加載到Web容器的內存辨萍,如果類庫不能共享,虛擬機的方法區(qū)就會很容易出現(xiàn)過度膨脹的風險返弹。
3)Web容器需要盡可能地保證自身的安全不受部署的Web應用程序影響锈玉。目前,有許多主流的Java Web容器自身也是使用Java語言來實現(xiàn)的义起。因此拉背,Web容器本身也有類庫依賴的問題,一般來說默终,基于安全考慮椅棺,容器所使用的類庫應該與應用程序的類庫互相獨立。
4)支持JSP應用的Web容器齐蔽,大多數(shù)都需要支持HotSwap功能两疚。我們知道,JSP文件最終要編譯成Java Class才能由虛擬機執(zhí)行含滴,但JSP文件由于其純文本存儲的特性诱渤,運行時修改的概率遠遠大于第三方類庫或程序自身的Class文件。而且ASP谈况、PHP和JSP這些網頁應用也把修改后無須重啟作為一個很大的“優(yōu)勢”來看待勺美,因此“主流”的Web容器都會支持JSP生成類的熱替換,當然也有“非主流”的碑韵,如運行在生產模式(Production Mode)下的WebLogic服務器默認就不會處理JSP文件的變化赡茸。
由于存在上述問題,在部署Web應用時祝闻,單獨的一個Class Path就無法滿足需求了占卧,所以各種Web容都“不約而同”地提供了好幾個Class Path路徑供用戶存放第三方類庫,這些路徑一般都以“l(fā)ib”或“classes”命名。被放置到不同路徑中的類庫华蜒,具備不同的訪問范圍和服務對象舷蒲,通常,每一個目錄都會有一個相應的自定義類加載器去加載放置在里面的Java類庫∮讯啵現(xiàn)在牲平,就以Tomcat容器為例,看一看Tomcat具體是如何規(guī)劃用戶類庫結構和類加載器的域滥。
在Tomcat目錄結構中纵柿,有3組目錄(“/common/*”、“/server/*”和“/shared/*”)可以存放Java類庫启绰,另外還可以加上Web應用程序自身的目錄“/WEB-INF/*”昂儒,一共4組,把Java類庫放置在這些目錄中的含義分別如下:
①放置在/common目錄中:類庫可被Tomcat和所有的Web應用程序共同使用委可。
②放置在/server目錄中:類庫可被Tomcat使用渊跋,對所有的Web應用程序都不可見。
③放置在/shared目錄中:類庫可被所有的Web應用程序共同使用着倾,但對Tomcat自己不可見拾酝。
④放置在/WebApp/WEB-INF目錄中:類庫僅僅可以被此Web應用程序使用,對Tomcat和其他Web應用程序都不可見卡者。
為了支持這套目錄結構蒿囤,并對目錄里面的類庫進行加載和隔離,Tomcat自定義了多個類加載器崇决,這些類加載器按照經典的雙親委派模型來實現(xiàn)材诽,其關系如下圖所示。
image.png
上圖中灰色背景的3個類加載器是JDK默認提供的類加載器恒傻,這3個加載器的作用已經介紹過了脸侥。而CommonClassLoader、CatalinaClassLoader盈厘、SharedClassLoader和WebappClassLoader則是Tomcat自己定義的類加載器睁枕,它們分別加載/common/*、/server/*扑庞、/shared/*和/WebApp/WEB-INF/*中的Java類庫譬重。其中WebApp類加載器和Jsp類加載器通常會存在多個實例拒逮,每一個Web應用程序對應一個WebApp類加載器罐氨,每一個JSP文件對應一個Jsp類加載器。
從圖中的委派關系中可以看出滩援,CommonClassLoader能加載的類都可以被Catalina ClassLoader和SharedClassLoader使用栅隐,而CatalinaClassLoader和Shared ClassLoader自己能加載的類則與對方相互隔離。WebAppClassLoader可以使用SharedClassLoader加載到的類,但各個WebAppClassLoader實例之間相互隔離租悄。而JasperLoader的加載范圍僅僅是這個JSP文件所編譯出來的那一個.Class文件谨究,它出現(xiàn)的目的就是為了被丟棄:當Web容器檢測到JSP文件被修改時,會替換掉目前的JasperLoader的實例泣棋,并通過再建立一個新的Jsp類加載器來實現(xiàn)JSP文件的HotSwap功能胶哲。
對于Tomcat的6.x版本,只有指定了tomcat/conf/catalina.properties配置文件的server.loader和share.loader項后才會真正建立Catalina ClassLoader和Shared ClassLoader的實例潭辈,否則在用到這兩個類加載器的地方都會用Common ClassLoader的實例代替鸯屿,而默認的配置文件中沒有設置這兩個loader項,所以Tomcat 6.x順理成章地把/common把敢、/server和/shared三個目錄默認合并到一起變成一個/lib目錄寄摆,這個目錄里的類庫相當于以前/common目錄中類庫的作用。這是Tomcat設計團隊為了簡化大多數(shù)的部署場景所做的一項改進修赞,如果默認設置不能滿足需要婶恼,用戶可以通過修改配置文件指定server.loader和share.loader的方式重新啟用Tomcat 5.x的加載器架構。
Tomcat加載器的實現(xiàn)清晰易懂柏副,并且采用了官方推薦的“正統(tǒng)”的使用類加載器的方式勾邦。如果讀者閱讀完上面的案例后,能完全理解Tomcat設計團隊這樣布置加載器架構的用意割择,那說明已經大致掌握了類加載器“主流”的使用方式检痰,那么筆者不妨再提一個問題讓讀者思考一下:前面曾經提到過一個場景,如果有10個Web應用程序都是用Spring來進行組織和管理的話锨推,可以把Spring放到Common或Shared目錄下讓這些程序共享铅歼。Spring要對用戶程序的類進行管理,自然要能訪問到用戶程序的類换可,而用戶的程序顯然是放在/WebApp/WEB-INF目錄中的椎椰,那么被CommonClassLoader或SharedClassLoader加載的Spring如何訪問并不在其加載范圍內的用戶程序呢?如果研究過虛擬機類加載器機制中的雙親委派模型沾鳄,相信讀者可以很容易地回答這個問題慨飘。
分析:如果按主流的雙親委派機制,顯然無法做到讓父類加載器加載的類去訪問子類加載器加載的類译荞,上面在類加載器一節(jié)中提到過通過線程上下文方式傳播類加載器瓤的。
答案是使用線程上下文類加載器來實現(xiàn)的,使用線程上下文加載器吞歼,可以讓父類加載器請求子類加載器去完成類加載的動作圈膏。看spring源碼發(fā)現(xiàn)篙骡,spring加載類所用的Classloader是通過Thread.currentThread().getContextClassLoader()來獲取的稽坤,而當線程創(chuàng)建時會默認setContextClassLoader(AppClassLoader)丈甸,即線程上下文類加載器被設置為AppClassLoader,spring中始終可以獲取到這個AppClassLoader(在Tomcat里就是WebAppClassLoader)子類加載器來加載bean尿褪,以后任何一個線程都可以通過getContextClassLoader()獲取到WebAppClassLoader來getbean了睦擂。