在OSGi傳說的第0章我們簡(jiǎn)要介紹過Bundle的生命周期,下面我們來簡(jiǎn)要回顧一下,下圖是其狀態(tài)轉(zhuǎn)換圖:
在上圖中,人工轉(zhuǎn)換箭頭線上的install、update、resolve、start、stop是通過在控制臺(tái)輸入對(duì)應(yīng)命令來觸發(fā)。Bundle生命周期過程之中的6種狀分別為:
1) UNINSTALLED(未安裝):狀態(tài)值為整數(shù)1。此時(shí)Bundle中的資源是不可用的。
2) INSTALLED(已安裝):狀態(tài)值為整數(shù)2。此時(shí)Bundle已經(jīng)通過了OSGi框架的有效性校驗(yàn)并分配了Bundle ID,本地資源已加載,但尚未對(duì)其依賴關(guān)系進(jìn)行解析處理。
3) RESOLVED(已解析):狀態(tài)值為整數(shù)4。此時(shí)Bundle已經(jīng)完成了依賴關(guān)系解析并已經(jīng)找到所有依賴包,而且自身導(dǎo)出的Package已可以被其它Bundle導(dǎo)入使用。在此種狀態(tài)的Bundle要么是已經(jīng)準(zhǔn)備好運(yùn)行,要么就是被停止了。
4) STARTING(啟動(dòng)中):狀態(tài)值為整數(shù)8。此時(shí)Bundle的BundleActivator的start()方法已經(jīng)被調(diào)用但是尚未返回。如果start()方法正常執(zhí)行結(jié)束,Bundle將自動(dòng)轉(zhuǎn)換到ACTIVE狀態(tài); 否則如果start()方法拋出了異常,Bundle將退回到RESOLVED狀態(tài)。
5) STOPPING(停止中):狀態(tài)值為整數(shù)16。此時(shí)Bundle的BundleActivator的stop()方法已經(jīng)被調(diào)用但是尚未返回。無論stop()是正常結(jié)束還是拋出了異常,在這個(gè)方法退出之后,Bundle的狀態(tài)都將轉(zhuǎn)為RESOLVED。
6) ACTIVE(已激活):狀態(tài)值為整數(shù)32。Bundle處于激活狀態(tài),說明BundleActivator的start()方法已經(jīng)執(zhí)行完畢,如果沒有其他動(dòng)作,Bundle將繼續(xù)維持ACTIVE狀態(tài)。
下圖中使用install命令安裝了位于C盤根目錄的Bundle(注意表達(dá)其位置的URL格式),在安裝完畢后控制臺(tái)返回了對(duì)應(yīng)的Bundle信息,包括ID、狀態(tài)、版本等元數(shù)據(jù)信息。
Class Loader在Bundle被正確解析(狀態(tài)變成Resolved)之后創(chuàng)建, 而只有Bundle 中的所有包約束條件都滿足后,對(duì)應(yīng)的Bundle才能被正確解析完畢。
Bundle的解析由定義在MANIFEST.MF文件中的四個(gè)配置項(xiàng)定義:
1) Import-Package:定義當(dāng)前Bundle需要導(dǎo)入的其它包(Package),一般位于其它Bundle之中并且被定義為Export-Package。
2) Export-Package:定義當(dāng)前Bundle要導(dǎo)出的包。被導(dǎo)出的包中的類資源可以被其它Bundle導(dǎo)入,可以被定義到Import-Package中。
3) Require-Bundle:定義當(dāng)前Bundle需要依賴的Bundle。
4) DynamicImport-Package:定義需要?jiǎng)討B(tài)導(dǎo)入的包。這里定義的包在Bundle解析過程中不會(huì)被使用到,而是會(huì)在運(yùn)行時(shí)被動(dòng)態(tài)解析并加載。
在 Bundle 得到正確解析后,OSGi 框架將會(huì)生成對(duì)應(yīng)Bundle的依賴關(guān)系表。使用bundle命令可以得到對(duì)應(yīng)的依賴關(guān)系說明,如下圖所示:
在MANIFEST.MF文件中定義的Bundle-Classpath會(huì)描述Classpath范圍,告訴Classloader去哪里查找類。
為了理解Classpath的配置,需要首先理解容器(Container)、條目(Entry)、條目路徑(Entry Path)、Bundle類路徑(Bundle Classpath)的概念:
1) 容器(Container):Jar包、Zip包、文件夾等,一般指Bundle的Jar包;
2) 條目(Entry):Container內(nèi)包含的資源,這些資源的組織是按層級(jí)進(jìn)行的。在運(yùn)行時(shí),一個(gè)Bundle中的Entry可能會(huì)來自多個(gè)容器,因?yàn)橐粋€(gè)Bundle可能會(huì)具有多個(gè)Fragment Bundle。
3) 條目路徑(Entry Path):在Bundle中查找Entry的順序如下:
● 首先查找Bundle的容器(如果有Fragment則先查找宿主Bundle)
● 然后按id的升序查找Fragment的容器(如果有Fragment的話)
這個(gè)查找資源的順序被稱為Entry Path。Entry Path標(biāo)識(shí)了資源在其容器中的路徑(位置),但其加載到OSGi環(huán)境中的順序并不是完全按這個(gè),而是按 Bundle Classpath。
4) Bundle類路徑(Bundle Classpath):指的是Bundle中資源在OSGi環(huán)境中加載的順序,它基于Entry Path來構(gòu)建。下圖是一個(gè)簡(jiǎn)單的樣例:
從上圖中能看出,在MANIFEST.MF中,Bundle-ClassPath由以逗號(hào)“,”分隔的各個(gè)Entry Path組成。
除了兩個(gè)jar包之外,還有一個(gè)特殊的Entry Path——小數(shù)點(diǎn)“.”。小數(shù)點(diǎn)Entry Path“.”也可以使用反斜杠“/”替代,兩者都是表示容器的根路徑。如果沒有指定任何Bundle-Classpath,容器的根路徑就是默認(rèn)值。
在Java中通過類加載器(Class Loader)把一個(gè)類裝入Java虛擬機(jī)中,每一個(gè)Java的Class對(duì)象都有一個(gè)指向其Class Loader的引用。類加載過程主要經(jīng)過三個(gè)步驟:
1) 裝載: 查找和導(dǎo)入類或接口的二進(jìn)制數(shù)據(jù);
2) 鏈接:又可以分成校驗(yàn)、準(zhǔn)備和解析三步,除了解析是可選的之外,其它步驟是嚴(yán)格按照順序完成的:
● 校驗(yàn):檢查導(dǎo)入類或接口的二進(jìn)制數(shù)據(jù)的正確性;
● 準(zhǔn)備:給類的靜態(tài)變量分配并初始化存儲(chǔ)空間;
● 解析:將符號(hào)引用轉(zhuǎn)成直接引用;
3) 初始化:激活類的靜態(tài)變量的初始化Java代碼和靜態(tài)Java代碼塊。
JVM規(guī)范定義了兩種類型的類裝載器:?jiǎn)?dòng)Class Loader(Bootstrap Class Loader)和用戶自定義Class Loader(User-defined Class Loader)。
Java提供了抽象類ClassLoader,它是所有用戶自定義Class Loader的父類。 System Class Loader是由JVM的實(shí)現(xiàn)者提供的一個(gè)特殊的用戶自定義Class Loader,它可以通過ClassLoader.getSystemClassLoader() 方法得到,如果編程者不特別指定裝載器則默認(rèn)使用它來裝載用戶類。
JVM在運(yùn)行時(shí)會(huì)產(chǎn)生三個(gè)ClassLoader:
1) BootClassLoader:用來裝載核心類庫(kù),如lang.*等,確保Java基本框架的可用。它用C++編寫而成,是JVM自帶的類加載器,在Java中不可見,表現(xiàn)為null。。
2) ExtClassLoader:用來加載擴(kuò)展類。ExtClassLoader的Parent為Bootstrap ClassLoader。
3) AppClassLoader: 用來加載用戶自定義類。AppClassLoader的Parent是ExtClassLoader。
每個(gè)Class Loader有自己的命名空間,命名空間由被此Class Loader加載的類組成,不同命名空間的兩個(gè)類是相互不可見的。不過如果能得到(例如通過反射)另一命名空間中的類所對(duì)應(yīng)的Class對(duì)象的引用,則還是可以訪問該類的成員變量。
由同一Class Loader裝載的屬于相同包的類組成了運(yùn)行時(shí)包(Runtime Package),決定兩個(gè)類是不是屬于同一個(gè)運(yùn)行時(shí)包,不僅要看它們的包名是否相同,還要看Class Loader是否相同。只有屬于同一運(yùn)行時(shí)包的類才能互相訪問包可見的類和成員。這樣的限制避免了用戶使用自己的代碼冒充核心類庫(kù)的類,進(jìn)而訪問核心類庫(kù)包可見成員的情況。
Bundle的加載策略、如何導(dǎo)入和導(dǎo)出代碼是在OSGi規(guī)范的模塊(Modules )層實(shí)現(xiàn)的。OSGi框架對(duì)于每個(gè)實(shí)現(xiàn)了 BundleActivator 接口的 Bundle(非Fragment Bundle)都會(huì)創(chuàng)建一個(gè)單獨(dú)的類加載器(Class Loader),不過對(duì)于Class Loader的創(chuàng)建可能會(huì)延遲到真正需要它時(shí)才會(huì)發(fā)生。
對(duì)應(yīng)的Classloader為每個(gè)Bundle都提供了獨(dú)立的命名空間來避免命名沖突,并且允許在不同的Bundle之間分享資源,這種機(jī)制使得在同一個(gè)Bundle中的資源都相互具有包(Package)級(jí)別的訪問權(quán)限。
關(guān)于OSGi類加載機(jī)制的細(xì)節(jié),可以參考OSGi規(guī)范中的“Runtime Class Loading”章節(jié)。
Bundle的Class Loader能加載的所有類的集合構(gòu)成了Bundle的類空間(Class Space)。Bundle 所需要的類資源應(yīng)該完全被其類空間所覆蓋,否則將會(huì)在運(yùn)行時(shí)環(huán)境中拋出類或資源未發(fā)現(xiàn)異常,從而導(dǎo)致 Bundle 無法正常工作。
類空間包含的類資源主要來自于以下幾個(gè)方面:
1) 父Class Loader可加載的類集合;
2) Import-Package定義的依賴的包中的類集合;
3) Require-Bundle定義的依賴的Bundle中的類集合;
4) Bundle自身的類集合,通常在Bundle-Classpath中定義;
5) 隸屬于Bundle的Fragment類集合。
Bundle的Class Loader搜索類資源的規(guī)則簡(jiǎn)要介紹如下:
1) 如類資源屬于* 包,則將加載請(qǐng)求委托給父 Class Loader;
2) 如類資源定義在 OSGi框架的啟動(dòng)委托列表(osgi.framework.bootdelegation)中,則將加載請(qǐng)求委托給父Class Loader;例如:
● osgi.framework.bootdelegation=*
● osgi.framework.bootdelegation=sun.*,com.sun.*
3) 如類資源屬于在 Import-Package 中定義的包,則框架會(huì)將加載請(qǐng)求委托給導(dǎo)出此包的Bundle的Class Loader;
4) 如類資源屬于在Require-Bundle 中定義的 Bundle,則框架會(huì)將加載請(qǐng)求委托給此Bundle的Class Loader;
5) Bundle搜索自己的Bundle-Classpath中定義的類資源;
6) Bundle搜索屬于該Bundle的Fragment的類資源;
7) 判斷是否找到導(dǎo)出(Export-Package)了對(duì)應(yīng)資源,如果仍未找到進(jìn)入動(dòng)態(tài)導(dǎo)入查找;
8) 若類在DynamicImport-Package中定義,則開始嘗試在運(yùn)行環(huán)境中尋找符合條件的 Bundle,框架會(huì)將加載請(qǐng)求委托給導(dǎo)出此包的Bundle的Class Loader;
● 注意DynamicImport-Package: *使用了星號(hào)來對(duì)所有資源進(jìn)行通配,這也意味著對(duì)應(yīng)的Bundle可以“看到”任何可能訪問到的資源,這個(gè)選項(xiàng)應(yīng)該盡量少地使用,除非別無它法。
9) 如果在經(jīng)過上面一系列步驟后,仍然沒有正確地加載到類資源,則OSGi框架會(huì)向外拋出類未發(fā)現(xiàn)異常。
在Equinox框架中引入了伙伴(Buddy)Bundle的概念,在類查找流程的最后,還會(huì)追加查找Buddy Bundle中的類資源。
伙伴策略(Buddy Policy)用于定義選擇伙伴類的策略,Equinox支持如下幾種策略:
● dependent——查詢所有直接或間接依賴當(dāng)前Bundle的Bundle。在Bundle數(shù)量很多的情況下使用該策略可能會(huì)導(dǎo)致性能問題。
● registered ——查詢所有依賴當(dāng)前Bundle并且明確將當(dāng)前Bundle注冊(cè)為伙伴(Buddy)的Bundle。它與dependent策略很類似,但是縮小了搜索的范圍。
● global ——查詢所有可用的導(dǎo)出包(exported packages)。
● app——查詢AppClassLoader。
● ext -——查詢ExtClassLoader。
● boot -——查詢BootClassLoader。
注意:在使用dependent和registered策略時(shí),Bundle中的所有包對(duì)其Buddy來說都是可用的,即使沒有被導(dǎo)出(export)也是如此。
對(duì)于需要導(dǎo)出資源的Bundle來說需要表態(tài)“我允許伙伴們來訪問自己的資源”,具體的表示方法為在MENIFEST.MF文件中增加Eclipse-BuddyPolicy 定義,其語(yǔ)法如下:
例如:Eclipse-BuddyPolicy: registered表示允許其它Bundle注冊(cè)為當(dāng)前Bundle的Buddy。
而對(duì)于需要依賴對(duì)應(yīng)Bundle資源的Buddy來說,則需要明確將自己聲明為對(duì)應(yīng)其它Bundle的Buddy,其語(yǔ)法如下:
例如:Eclipse-RegisterBuddy: org.hibernate 表示當(dāng)前Bundle為名為org.hibernate的Bundle的Buddy。
注意:如果希望Bundle X注冊(cè)成為Bundle Y的伙伴,則需要滿足如下條件:
1) BundleY必須定義registered為Eclipse-BuddyPolicy;
2) Bundle X必須在Eclipse-RegisterBuddy中定義Bundle Y的名稱(例如:Eclipse-RegisterBuddy: Y);
3) Bundle X所依賴的包必須在Bundle Y中被導(dǎo)出(可以通過Require-Bundle或Import-Package來定義依賴關(guān)系)
示意圖如下:
Buddy類加載機(jī)制是Equinox框架獨(dú)有的特性,與其它OSGi實(shí)現(xiàn)并不兼容,也不屬于OSGi標(biāo)準(zhǔn)規(guī)范,它的一般應(yīng)用場(chǎng)景是便于Bundle使用那些并非最初就設(shè)計(jì)為在OSGi環(huán)境中使用的包(例如一般Jar包)。使用此特性需要注意,你可能會(huì)因此破壞OSGi Bundle的一致性要求。
聯(lián)系客服