什麼是模塊化html
與面向對象同樣,模塊化的目的也是鬆耦合,高內聚。咱們能夠理解爲模塊化是將對象間的互訪作了邊界劃分,即對一組業務相關的對象進行封裝,而且提供可能的更高層次的代碼訪問隔離機制。java
物理模塊化 VS 邏輯模塊化shell
物理模塊化是指應用中的類文件被物理的分割放在不一樣的模塊中,可是每一個模塊間的互訪不受控制,各個模塊能夠訪問模塊間的內部對象,只要對象是可訪問的。只是是對代碼自己進行模塊化管理。編程
例如JAVA中,應用被分爲模塊A和B,模塊B中有一個public對象B.b,該對象能夠徹底被模塊A訪問,由於它是public的。緩存
邏輯模塊化是指在物理模塊化的基礎上,對模塊進行控制訪問;即模塊間實現了訪問隔離,而這纔是咱們所說的真正的模塊化的概念。框架
再看回上面的例子,若是B.b沒有定義是其它模塊可訪問的,那麼默認A.a是訪問不到B.b這個對象的,無論這個對象的訪問級別是什麼。jvm
OSGI的做用模塊化
在java中,OSGI是一個實現java模塊化互訪的平臺,咱們能夠理解爲是一個更高級的JVM。它提供了邏輯上的模塊化控制。工具
OSGI對模塊的定義ui
在OSGI中,模塊稱之爲bundle,一個bundle在物理上而言就是一個jar包。Jar包中有一個描述jar包的信息文件,位於jar內部的META-INF目錄下的MANIFEST.MF文件。OSGI經過MANIFEST這個文件獲取模塊的定義信息,好比模塊間的互訪信息,模塊的版本信息等。
Note:對於MANIFEST.MF文件的操做,因爲這個文件有不少使用約束,好比一行不能超過72個字符,因此通常都是經過IDE工具對它進行編輯
bundle裏有什麼
一個bundle中通常包含以下的東西:
部署描述文件(MANIFEST.MF,必要的),各種資源文件(如html、xml等,非必須的),還有類文件。這與一個普通的jar包沒有任何的區別。可是,除此以外,bundle裏還能夠放入其它的jar包,用於提供給bundle內部的類引用,即bundle內部的lib庫。(跟一個war很相似)。
Note:實際上bundle裏能夠存聽任何的內容,可是在bundle內部不會有嵌套的bundle,即上面提到的存放於bundle中的jar包就只會當成是一個普通的jar包,無論這些jar包中是否含有bundle定義的信息
l MANIFEST.MF的定義
MANIFEST.MF位於bundle中的根目錄下的META-INF目錄下。
一個bundle的必要信息定義以下:
咱們稱這些內容爲bundle的頭信息,具有了這4個頭信息就是一個bundle
其中,
Bundle-ManifestVersion表示OSGI的參考版本,2表明參考版本使用的是OSGI R4.0+
Bundle-SymbolicName用於做爲bundle的ID標識的前綴,通常用模塊的頂級包名來命名
Bundle-Version表示bundle當前的版本,並做爲bundle的ID標識的後綴
Bundle-Name則是一個可讀的bundle的命名定義,沒有太大的做用
其它還有不少的頭信息的定義,這裏就不一一列舉了。
頭信息的格式:
主要由頭屬性名稱加冒號和各個從句組成,從句間用逗號分隔
Property-Name: clause, clause, clause …
從句的定義:
target; param1(:)=value1; param2(:)=value2 …
target表示頭屬性對應的值,後面能夠帶上不少不一樣的參數對,每一個參數分爲參數名和參數值,而且都用分號分隔
參數對的定義:
參數分爲兩種,屬性和指令,若是是指令,須要在=號前加上冒號表示是一個指令
attr1=value1
dir1:=value1
頭信息的簡寫形式:
若是頭信息的全部target的參數對定義都是同樣的,那麼能夠將target先定義在前面,全部的參數對定義在最後
Property-Name: target1; target2; attr1=value1; dir1:=value1 ….
Bundle的Identifier
即bundle的ID標識,這個是用來在OSGI中對bundle進行惟一性的定義的。在Bundle被OSGI讀取後,OSGI經過對Bundle-SymbolicName和Bundle-Version進行組合,成爲一個惟一的ID標識。組合的方式爲<Bundle-SymbolicName>_<Bundle-Version>。
Bundle-SymbolicName的命名方式
Bundle-SymbolicName沒有任何的命名要求,能夠是任意的字符組成,可是通常是用模塊的頂級包名來做爲它的名稱
Bundle-Version的命名方式
Bundle-Version有本身的命名要求,格式爲[0-9].[0-9].[0-9].{ConstraintName}
前面都是數字做爲版本號,最後一個小數點後的名稱是通過命名約束的版本名
例如1.2.3.alpha,1表明大版本號,2表明中版本號,3表明小版本好,而alpha表明小版本中的某個階段,好比alpha,beta,snapshot,qualifier等,下圖展現了版本大小的排序方式
代碼訪問機制設置
JVM環境:
類之間的互訪是經過設置classpath來查找的
OSGI環境:
1)bundle內部的classpath
默認是bundle的根目錄,可是能夠經過頭信息來指定bundle內的多個目錄
如圖所示,經過配置Bundle-ClassPath進行設置,. 表明bundle的根目錄,是必需要有的,customPath表明<bundle>/customPath目錄,固然,也能夠是具體到某個jar文件。將會按順序查找
2) Export-Package
經過設置Export-Package的頭信息進行設置,能夠設置多個包,只有在這裏定義的包內的類能夠被其它的模塊進行訪問。可是隻能訪問該包下的,該包的子包的類是不會被曝露的。
例如Export-Package: net.georgezeng.test.modelb.service; version=1.0.0, net.georgezeng.test.modelb.dao; version=1.2.0
3) Import-Package
當某個模塊曝露了某些包,那麼若是你要引用相應的那個模塊下的包的類的話,就須要通知OSGI引入你設置的包到該模塊,即在該模塊的MANIFEST中定義Import-Package頭信息。
例如上面的模塊曝露了兩個包,分別是service和dao,咱們這裏假設該模塊須要引入service的包來獲取當中的類的訪問,那麼就須要以下設置
Import-Package: net.georgezeng.test.modelb.service; version=1.0.0
這樣就能訪問該service包下的類了。
Note:該頭信息有一個有用的指令resolution,默認值是mandatory,能夠設置爲optional,當設置爲optional時,依賴解析將對其忽略(下面將會提到)
4) DynamicImport-Package
使用此頭信息時,OSGI將會掃描該頭信息設置的全部包
它只能設置target值,能夠多個,而且target值可使用通配符,例如
DynamicImport-Package: net.gerogezeng.test.*, net.georgezeng.test2 …
OSGI將會掃描net.georgezeng.test下全部的子包,但不包括test包下自己的類,若是要用到test包下的類,就是第二個target所設置的值的方式
Note:使用該頭信息時,依賴解析也會對其忽略
5) Require-Bundle
使用Require-Bundle至關因而將被require的Bundle export出來的包所有import
關於Export和Import中version屬性的定義和匹配
在上面的描述中咱們看到了Export和Import中都有一個version的屬性定義,該version採用的命名規則與Bundle-Version是同樣的,這裏主要是說明如何設置它的範圍
如圖,你能夠這樣設置
Import-Package: net.georgezeng.test.modelb.service; version=」[1.0.0, 2.0.0)」
Export和Import間的匹配
1) 版本匹配
經過version屬性進行設置,經過上圖設置的版本的範圍比較進行匹配
2) 自定義屬性匹配
自定義屬性的匹配必須是屬性名和屬性值都相同,即Import中的屬性export中也是有的,且屬性值相同
Note:Import-Package的規則也適用於Require-Bundle
依賴和解析
何爲依賴?
若是BundleA Export了某個包,而BundleB Import了這個包,那麼就說BundleB依賴於BundleA
或者BundleA中設置了Require-Bundle的頭信息,那麼Require-Bundle中的全部相關的target Bundle都被A依賴
Bundle的依賴解析
當OSGI讀取Bundle時,Bundle將會被解析,只有當Bundle被正確解析成功,才能被其它Bundle使用或者運行。即OSGI獲取Bundle的MANIFEST裏的頭信息進行檢查分析,而這個解析是連鎖式反應的。
例如BundleB依賴於BundleA,假設OSGI要使用BundleB,它須要先解析BundleB,而BundllerB由於是依賴於BundleA的,因此BundleA會先於BundleB被進行解析,若是BundleA解析成功,纔會繼續解析BundleB,若是BundleB也成功解析,那麼OSGI就能夠正常使用BundleB了,這個過程因爲會引發關聯的Bundle進行解析,因此是依賴解析。
DynamicImport vs optional
上面提到了能夠經過Import-Package添加resolution指令設爲optional或經過DynamicImport-Package進行配置令使得OSGI在進行依賴解析時對其進行忽略。
他們的相同之處都是在運行期間當某個Bundle用到了他們export的包時纔會去解析,若是exported的Bundle以前已經解析成功,那麼就直接搜索class,若是還未解析過,那麼optional只會解析一次,若是失敗了,就不會再嘗試解析它,直到它已被從新解析過;而Dynamic的方式則是每次都嘗試從新解析
依賴匹配
Bundle間的依賴須要進行匹配校驗,好比bundleA須要某個包,而BundleB曝露了這個包,可是BundleC也曝露了相同的包,那麼這時BundleA究竟是要依賴哪一個呢?這個時候就是經過上面提到的export和import間的匹配來進行校驗的。默認會對比version屬性,若是import中有自定義屬性,那麼export中也必須有相應的自定義屬性且屬性值相同才能匹配成功
匹配的順序:
1)對version屬性進行範圍匹配,若是不止一個bundle匹配成功,則進入下一步
2)若是有自定義屬性,進行嚴格的自定義屬性匹配,若是不止一個bundle匹配成功則進入下一步
3)查找Bundle-Version更高的Bundle,以最高的版本爲主
Fragment Bundle
一個模塊就是一個Bundle,但有時候咱們會須要將Bundle在物理上分紅多個塊,並且讓細分出來的塊屬於同一個模塊下,那麼這個時候就有了Fragment的概念。
Fragment沒辦法獨立存在於OSGI中,須要依附於某個Bundle下,即有一個Bundle做爲宿主。一個Fragment只能依附於一個Bundle。
Note:Fragment自己沒有本身的classloader,使用的是Bundle的classloader
Class的搜索順序
在瞭解整個class的搜索路徑前,咱們須要先了解下面2個內容:
1) Bundle的Classloader
在OSGI環境中,一個Bundle有一個ClassLoader,用於讀取bundle內部的類文件
2) boot delegation
在OSGI的配置文件中咱們能夠配置一個選項用於將須要的包委派給jvm的classloader進行搜索
例如org.osgi.framework.bootdeletation=sun.*,com.sun.*
Bundle請求一個class的順序以下:
1)若是請求的class來自於java.*下,那麼bundle的classsloader會經過父classsloader(即jvm的classloader)進行搜索,找不到則拋錯,若是不是java包下的,則直接進入2)
2)若是請求的class非java包下的,那麼將搜索boot delegation的設置的包,一樣也是經過1)的classloader進行搜索,搜索不到則進入下一步
3)經過Import-Package對應的Bundle的classloader進行搜索,搜索不到則進入下一步
4)經過Require-Bundle對應的Bundle的classloader進行搜索,搜索不到則進入下一步
5)經過Bundle自身的classpath進行搜索,若是搜索不到則進入下一步
6)經過Fragment的classpath進行搜索,若是找不到則進入下一步
7)若是設置了DynamicImport-Package的頭信息,將經過掃描DynamicImport-Package中設置的包,而後去相應的export的Bundle中查找,有則返回,沒有則拋錯,搜索完畢;若是沒有設置該頭信息,則直接拋錯
Bundle的生命週期
如圖,橢圓表示狀態,bundle在OSGI中一共能夠查看到5個狀態,分別是Installed,Resolved,starting,Active,stopping。Uninstalled則是一個虛的狀態,即當Bundle不存在於OSGI中時的狀態。
OSGI裏對Bundle的操做一共有6個,分別是Install,Update,Refresh,Start,Stop,Uninstall
經過這些操做就能夠達到不一樣的狀態
如何安裝和啓動Bundle?
能夠經過3種方式:
1)OSGI的配置文件
2)OSGI的shell
3)OSGI的API
關於Resolved
上面提到解析就是從Installed到Resolved間的動做,Resolved表示解析成功,進入已解析狀態,此狀態下Bundle能夠被其它Bundle使用或啓動運行Bundle自身
只有在Resolved狀態時當調用start命令後才能激活starting狀態,並在調用結束後自動轉入Active狀態,若是starting時有異常則自動返回Resolved狀態
Active狀態
只有當在Active狀態時,調用stop命令才能激活stopping的狀態,並在結束後(不論是否有異常)自動轉入Resolved狀態
Starting和stopping
這兩個狀態能夠交由developer控制,好比對資源進行載入和釋放,作一些bundle初始化的事情。
Note:
1)若是Bundle是經過配置文件啓動的話,那麼Bundle將會經過start level event dispatcher的線程進行starting的操做,而且bunlde間的start是按順序的,可是因爲不是在OSGI的console線程中啓動,因此在starting過程當中能夠對shell進行操做。
2)若是Bunlde是經過shell進行啓動的,OSGI將使用shell的當前線程,即OSGI Console線程進行啓動,在該狀態中shell將沒法作任何操做。若是在starting中bundle出現死鎖或須要長時間的過程那麼將會致使沒法對shell進行操做,從而只能選擇重啓OSGI環境。
3)因爲stopping是不可能在OSGI啓動時發生的,通常都是在shell下經過stop命令調用發生,這種狀況與2)相同。
因此要慎重的考慮starting和stopping的邏輯要如何處理。
在starting和stopping的狀態下調用任何相關的操做都是不合理的
關於update和refresh
Update會對bundle內容進行更新,refresh不會對Bundle內容進行更新
1)當Bundle處於Installed狀態時,調用update保持狀態不變,調用refresh將會自動進行解析,若是成功則進入resolved,不然仍是處於installed
2)當Bundle處於Resolved時,調用update命令將會回到installed狀態,而調用refresh時會再次解析,成功則自定啓動bundle,直到進入active
3)當Bundle處於Active狀態時,調用update後若是解析成功將返回active狀態,refresh同樣
Note:使用refresh會形成依賴解析的發生
關於Activator
Starting和stopping究竟是在哪裏進行定義呢?OSGI定義了一個Activator對starting和stopping進行操做,這個類是org.osgi.framework.BundleActivator。只要繼承該類,並在MANIFEST.MF中定義Bundle-Activator頭信息便可實現
關於BundleContext
經過BundleContext咱們能夠與OSGI框架進行交互,好比獲取OSGI下當前的bundle列表。
一個Bundle都有一個對應的BundleContext,當Bundle被start的時候建立且在stop的時候被銷燬。每啓動一次Bundle都將得到一個新的BundleContext
如何獲取BundleContext?
在Activator中,OSGI將會在start和stop方法中傳入這個對象
關於event
生命週期的控制總會有一些event能夠處理,好比經過實現OSGI提供的listener接口便可,這裏不詳細熬述
OSGI的緩存
OSGI對於Bundle的install操做是持久化的操做,即當install某一個bundle的時候,OSGI將會把該bundle進行副本保存,而再也不須要當前的bundle的jar包。這是OSGI的默認行爲,當OSGI中止再啓動時,此Bundle將會自動載入保存的副本。
在update的時候OSGI將會保存一個新的bundle副本,可是老的副本也還在(不過只在當前的OSGI環境下存在了,重啓則會找到最新的副本),由於可能有其它的bundle依賴於老副本存在,此時須要使用refresh來進行同步刷新。這種會引發依賴解析再次進行。
若是使用uninstall則bundle的副本再也不保存
Note:能夠經過添加-clean的啓動參數來清理緩存
Bundle的persistent storage area
OSGI會爲bundle開闢一塊持久化數據區域,用於給bundle進行資源的存取,這塊區域經過BundleContext進行訪問,並在uninstall的時候銷燬
OSGI Service
什麼是OSGI Service?
咱們有了export和import,已經能夠很好的管理模塊間的代碼互訪,可是,若是咱們只但願曝露接口,而不是實現的話,如何讓依賴的Bundle得到接口的實現呢?這個時候咱們就須要一種相似服務查找的方式經過OSGI來得到實現了。
因此Bundle有兩種角色:生產者和消費者
生產者Bundle經過註冊服務將實現注入OSGI中,消費者Bundle則是經過查找服務的方式從OSGI中得到實現。
OSGI目前提供了2種方式用於註冊和查找服務:
1)編程式
經過BundleContext進行註冊或查找
2)聲明式
是OSGI R4.0中引入的新的方式,經過XML文件的配置進行服務的註冊和查找