前言:
本次博客主要是對Tomcat與OSGi的類加載器架構,所以就需要對tomcat、OSGi以及類加載機制有所了解
類加載可以在http://www.cnblogs.com/ghoster/p/7594224.html中簡單了解
一、Tomcat:正統的類加載架構
1.主流的Java Web服務器,如Tomcat、Jetty、WebLogic、WebSphere等都實現了自己定義的類加載器(一般都不止一個)。因為一個功能健全的web服務器要解決一下幾個問題:
1)部署在一個服務器上的兩個web應用程序所使用的Java類庫可以實現互相隔離。這是最基本的需求,兩個不同的應用程序可能會依賴同一個第三方類庫的不同版本,不能要求一個類庫在一個服務器中只有一份,服務器應當保證兩個應用程序的類庫可以互相獨立使用
2)部署在同一個服務器上的兩個Web應用程序所使用的Java類庫可以互相共享。這個需求也非常很常見,如,用戶可能有10各使用Spring組織的應用程序部署在同一台服務器上,如果把10份Spring分別存放在各個應用程序的隔離目錄中,將會是很大的資源浪費-這主要不是浪費磁盤空間的問題,而是指類庫在使用時都要被加載到服務器內存,如果類庫不能共享,虛擬機的方法區就會很容易出現過度膨脹的風險
3)服務器需要盡可能地保證自身的安全不受部署的Web應用程序影響。目前,有許多主流的Java Web服務器自身也是使用Java語言來實現的。因此,服務器本身也有類庫依賴的問題,一般來說,基於安全考慮,服務器所使用的類庫應該與應用程序的類庫相互獨立
4)支持jsp應用的Web服務器,大多數都需要支持HotSwap功能。我們知道,jsp文件最終要編譯成Java Class才能由虛擬機執行,但jsp文件由於其純文本存儲的特性,運行時修改的概率遠遠大於第三方類庫或程序自身的Class文件。而且ASP、PHP和JSP這些網頁應用也把修改后無需重啟作為一個很大的“優勢”來看待,因此,“主流”的Web服務器都會支持JSP的熱替換,當然也有“非主流”的,如運行在生產模式下的WebLogic服務器默認就不會處理JSP文件的變化
由於存在上述問題,在部署Web應用時,單獨的一個ClassPath就無法滿足需求了,所以各種Web服務器都“不約而同”地提供了好幾個ClassPath路徑供用戶存放第三方類庫,這些路徑一般都以lib或classes命名,被放置到不同路徑中的類庫,具備不同的訪問范圍和服務對象,通常,每一個目錄都會有一個相應的自定義類加載器去加載放置在里面的Java類庫。
2.Tomcat對用戶類庫與類加載器的規划
在其目錄結構下有三組目錄(“/common/*”、“/server/*”、“/shared/*”)可以存放Java類庫,另外還可以加上Web應用程序本身的目錄“/WEB-INF/*”,一共4組,把Java類庫放置在這些目錄中的含義分別如下:
1)放置在/commom目錄中:類庫可被Tomcat和所有的Web應用程序共同使用
2)放置在/server目錄中:類庫可被Tomcat使用,對所有的Web應用程序都不可見
3)放置在/shared目錄中:類庫可被所有的Web應用程序所共同使用,但對Tomcat自己不可見
4)放置在/WebApp/WEB-INF目錄中:類庫僅僅可以被此Web應用程序使用,對Tomcat和其他Web應用程序都不可見
為了支持這套目錄結構,並對目錄里面的類庫進行加載和隔離,Tomcat自定義了多個類加載器,這些類加載器按照經典的雙親委派模型來實現,所下圖:
最上面的三個類加載器是JDK默認提供的類加載器,這三個加載器的的作用之前也說過,這里不再贅述了,可以在http://www.cnblogs.com/ghoster/p/7594224.html簡單了解。而CommonClassLoader、CatalinaClassLoader、SharedClassLoader和WebAppClassLoader則是Tomcat自己定義的類加載器,他們分別加載/common/*、/server/*、/shared/*和/WebApp/WEB-INF/*中的Java類庫。其中WebApp類加載器和jsp類加載器通常會存在多個實例,每一個Web應用程序對應一個WebApp類加載器,每一個jsp文件對應一個Jsp類加載器
從上圖的委派關系可以看出,CommonClassLoader能加載的類都可以被CatalinaClassLoader和SharedClassLoader使用,而CatalinaClassLoader和SharedClassLoader自己能加載的類則與對方相互隔離。WebAppClassLoader可以使用SharedClassLoader加載到的類,但各個WebAppClassLoader實例之間相互隔離。而JasperLoader的加載范圍僅僅是這個JSP文件所編譯出來的哪一個Class,它出現的目的就是為了被丟棄:當服務器檢測到JSP文件被修改時,會替換掉目前的JasperLoader的實例,並通過在建立一個新的Jsp類加載器來實現JSP文件的HotSwap功能
二、OSGi:靈活的類加載器架構
既然說到OSGI,就要來解釋一下OSGi是什么,以及它的作用
OSGi(Open Service Gateway Initiative):是OSGi聯盟指定的一個基於Java語言的動態模塊化規范,這個規范最初是由Sun、IBM、愛立信等公司聯合發起,目的是使服務提供商通過住宅網管為各種家用智能設備提供各種服務,后來這個規范在Java的其他技術領域也有不錯的發展,現在已經成為Java世界中的“事實上”的模塊化標准,並且已經有了Equinox、Felix等成熟的實現。OSGi在Java程序員中最著名的應用案例就是Eclipse IDE
OSGi中的每一個模塊(稱為Bundle)與普通的Java類庫區別並不大,兩者一般都以JAR格式進行封裝,並且內部存儲的都是Java Package和Class。但是一個Bundle可以聲明它所依賴的Java Package(通過Import-Package描述),也可以聲明他允許導出發布的Java Package(通過Export-Package描述)。在OSGi里面,Bundle之間的依賴關系從傳統的上層模塊依賴底層模塊轉變為平級模塊之間的依賴(至少外觀上如此),而且類庫的可見性能得到精確的控制,一個模塊里只有被Export過的Package才可能由外界訪問,其他的Package和Class將會隱藏起來。除了更精確的模塊划分和可見性控制外,引入OSGi的另外一個重要理由是,基於OSGi的程序很可能可以實現模塊級的熱插拔功能,當程序升級更新或調試除錯時,可以只停用、重新安裝然后啟動程序的其中一部分,這對企業級程序開發來說是一個非常有誘惑性的特性
OSGi之所以能有上述“誘人”的特點,要歸功於它靈活的類加載器架構。OSGi的Bundle類加載器之間只有規則,沒有固定的委派關系。例如,某個Bundle聲明了一個它依賴的Package,如果有其他的Bundle聲明發布了這個Package,那么所有對這個Package的類加載動作都會為派給發布他的Bundle類加載器去完成。不涉及某個具體的Package時,各個Bundle加載器是平級關系,只有具體使用某個Package和Class的時候,才會根據Package導入導出定義來構造Bundle間的委派和依賴
另外,一個Bundle類加載器為其他Bundle提供服務時,會根據Export-Package列表嚴格控制訪問范圍。如果一個類存在於Bundle的類庫中但是沒有被Export,那么這個Bundle的類加載器能找到這個類,但不會提供給其他Bundle使用,而且OSGi平台也不會把其他Bundle的類加載請求分配給這個Bundle來處理
一個例子:假設存在BundleA、BundleB、BundleC三個模塊,並且這三個Bundle定義的依賴關系如下:
BundleA:聲明發布了packageA,依賴了java.*的包
BundleB:聲明依賴了packageA和packageC,同時也依賴了Java.*的包
BundleC:聲明發布了packageC,依賴了packageA
那么,這三個Bundle之間的類加載器及父類加載器之間的關系如下圖:
由於沒有涉及到具體的OSGi實現,所以上圖中的類加載器沒有指明具體的加載器實現,只是一個體現了加載器之間關系的概念模型,並且只是體現了OSGi中最簡單的加載器委派關系。一般來說,在OSGi中,加載一個類可能發生的查找行為和委派關系會比上圖中顯示的復雜,類加載時的查找規則如下:
1)以java.*開頭的類,委派給父類加載器加載
2)否則,委派列表名單內的類,委派給父類加載器加載
3)否則,Import列表中的類,委派給Export這個類的Bundle的類加載器加載
4)否則,查找當前Bundle的ClassPath,使用自己的類加載器加載
5)否則,查找是否在自己的Fragment Bundle中,如果是,則委派給Fragment bundle的類加載器加載
6)否則,查找Dynamic Import列表的Bundle,委派給對應Bundle的類加載器加載
7)否則,查找失敗
從之前的圖可以看出,在OSGi里面,加載器的關系不再是雙親委派模型的樹形架構,而是已經進一步發展成了一種更復雜的、運行時才能確定的網狀結構。