OSGi入门篇:模块层
1 什么是模塊化
模塊層是OSGi框架中最基礎的一部分,其中Java的模塊化特性在這一層得到了很好的實現。但是這種實現與Java本身現有的一些模塊化特性又有明顯的不同。 本文介紹模塊層的一些基礎知識,以及OSGi聯盟在設計模塊層時所做的一些考慮。OSGi標準走到今天,并不是憑空想出來的,它的產生恰恰是為了彌補之前一些技術的缺陷。
模塊化其實就是計算機科學中常見的一個概念: “將一個大型系統分解為多個較小的互相協作的邏輯單元,通過強制設定模塊之間的邏輯邊界來改善系統的維護性和封裝性”。
在OSGi中模塊的定義可以參考下圖:
也就是說一個模塊(module)定義了一個邏輯邊界,這種模塊本身精確的控制了哪些類是完全被封裝起來的,而哪些類需要暴露出來作為外部使用。這樣我們就可以輕松的將實現屏蔽在模塊的內部,而將公共API暴露在外部。
2 為什么需要模塊化
2.1 OSGi中模塊化與面向對象的聯系與區別
按照以上模塊化的定義,可能有的人會問:“在面向對象里面,不是也有對模塊化的支持嗎?”沒錯,面向對象的概念可以說也在一定程度上支持模塊化編程,那為什么還需要OSGi提供的模塊化特性呢?這涉及到“邏輯邊界”的不同粒度。
在用Java編寫面向對象程序的時候,一個了解面向對象概念的人是不會把所有功能都塞到同一個類里面去的,面向對象讓你從問題域中發現多個事物,并且每個事物負責不同的功能,盡量做到高內聚和低耦合。在這里,我們可以說面向對象的模塊化粒度是在“類”這個級別上。?
而OSGi的模塊化,則是通過為JAR包添加metadata來定義哪些類應該暴露哪些類又隱藏在包中,其控制可見性的粒度是在bundle(JAR包)這一層面上的。
所以,它們所帶來的能力都是通過控制可見性和可用性來保證高內聚和低耦合的,但是粒度不同,一個是對象層面上的,一個是模塊層面上的。 既然負責的是不同的粒度,那么兩者并不相互沖突,各有各的作用在里面。
2.2 Java在模塊化方面的局限性
2.2.1 底層代碼可見性控制
Java提供了private,public,protected和package private(無修飾符)這四種訪問控制級別,不過這僅僅提供了底層的OO數據封裝特性。包這個概念確實是起到了分割代碼的作用,但是如果包中的代碼需要對包外可見,那么必須設置為public(或者protected,如果是使用了繼承的話)。 這樣的話就可能出現一個問題:
首先大家看看下面的例子,其中有三個java文件:?
org.serc.helloworld.Hello.java:定義了一個接口
org.serc.helloworld.impl.HelloImpl.java:實現了Hello接口
package org.serc.helloworld.impl;import org.serc.helloworld.Hello;public class HelloImpl implements Hello{ final String helloString;public HelloImpl(String helloString){this.helloString = helloString; }public void sayHello(){System.out.println(this.helloString); } }org.serc.helloworld.main.Main.java package org.serc.helloworld.main;import org.serc.helloworld.Hello; import org.serc.helloworld.HelloImpl;public class Main{ final String helloString;public static void main(String[] args){Hello hello = new HelloImpl(“Hello,SERC!”);hello.sayHello(); }}
這三個文件分別在不同的包中。按理說,HelloImpl這個實現細節是不應該暴露給其他包的,但是從Main.java的main方法中我們可以明顯的看出,為了創建Hello 的實例,我們不得不引入HelloImpl類,但是HelloImpl作為接口的實現細節,是不應該暴露給使用者的,這違反了封裝的原則,顯然不太好。
但是,如果我們不想讓HelloImpl暴露出來的話,就需要做額外的工作來保證“既隱藏了實現細節,又能簡單的創建一個實現了Hello接口的實例”。達到這一目的的方法不止一種(比如工廠模式),有些至今也是很常用的,但是這增加了與應用本身功能無關的多余工作,想想如果你每次想開發一個應用都要為了達到上述目的而做出多余工作,不得不說是有點繁瑣的,所以這可以說是Java的一大局限。
2.2.2 classpath的局限
我們在classpath中加入jar包的時候,只是簡單的給出文件路徑,而這個jar包的版本和一致性,它所依賴的jar包是什么,我們都無法在classpath中明確的設置或是從classpath中看出這些屬性。?
并且classpath中的jar包是按序加載的,例如:
classpath=c:\servlet2.2\servlet.jar;c:\servlet2.3\servlet.jar,
那么在實際應用的過程中,Java讓你使用的是servlet2.2,而不是servlet2.3。這種情況下我們還能看出來使用的是哪個版本,如果在大型系統中大家分開開發的時候各用各的servlet包,并且版本號不一樣,那么在最后將開發結果合并的時候,到時候用的是哪個版本的servlet包就很難搞清楚了,也就說不可控性是比較強的。?
即使classpath能注意到版本的問題,也沒法精確指出依賴。試著回想你在設置classpath的過程中出現過情況:在你以為classpath已經設置完畢以后,你嘗試啟動程序,結果虛擬機拋出異常告訴你缺包,然后你再加上你覺得缺少的那些包,然后再啟動程序,如此反復直到虛擬機不運行到缺包異常為止。
2.3 OSGi對這些局限性的改善
對于上一小節提到的Java的局限,在OSGi中都得到了很好的解決。
- 包的可見性:OSGi通過引入包的可見性機制,能夠完全控制一個包中的代碼對哪些模塊可見,而不僅僅局限于無差別的可見性,從而完善了Java的代碼訪問控制機制。
- 包的版本: OSGi通過為包增加版本信息,可以精確控制代碼的依賴,保證代碼的版本一致性,彌補了classpath的缺點。
3 OSGi模塊層基礎
3.1 Bundle的概念
這是模塊層最核心的概念,也是模塊(module)這個概念在OSGi中的具象表現。接下來你將會在OSGi的世界中創建和使用數不勝數的bundle。?
什么是bundle?——bundle是以jar包形式存在的一個模塊化物理單元,里面包含了代碼,資源文件和元數據(metadata),并且jar包的物理邊界也同時是運行時邏輯模塊的封裝邊界。
一個更為直觀的說明:在標準的jar包的manifest文件中添加一些bundle的模塊化特征(就是前面提到的metadata)后,這個jar包就變成了一個bundle。?
那么有了上面的描述,你大概也能想到,bundle和普通jar包最大的區別就在于元數據。
3.2 使用元數據來定義bundle
Bundle元數據的目的在于準確描述模塊化相關的bundle特征,這樣才能讓OSGi框架恰當的對bundle進行各種處理工作(比如依賴解析,強制封裝等),這些元數據主要有這三個部分:
- 可讀的信息(可選):幫助更好地理解和使用bundle
- bundle的標識符(必須):唯一的標識一個bundle
- 代碼可見性(必須):定義內部與外部代碼
3.2.1 可讀的信息
這些內容可以幫助人們直觀的了解這個bundle是做什么的,從哪里來。OSGi標準定義了幾個元數據條目來達到這個目的,但是所有的條目都不是必須的,并且也不對模塊化特性產生任何的影響,OSGi框架會完全無視這些內容。?
下面就是一個這類信息的例子:
3.2.2 bundle的標識符
在上個小節提到的可讀信息中,有些似乎也能用來唯一標識一個bundle,比如Bundle-Name,但是事實上,他并沒有被用來標識一個bundle。早期的OSGi標準中并沒有提供標識一個已知bundle的方法,直到OSGi R4標準“唯一bundle標識符”這個東西才被提出來。為了向后兼容,Bundle-Name就不能用來作為標識符了,否則就會增加維護向后兼容的工作,所以新的manifest屬性就誕生了:Bundle-SymbolicName
Bundle-SymbolicName: org.serc.helloworld?
兩者相比,Bundle-Name是給用戶讀的,而Bundle-SymbolicName是給OSGi框架讀的,讓OSGi框架能夠唯一標識一個bundle。
只用一個Bundle-SymbolicName肯定是可以唯一標識一個bundle了,但是隨著時間的推移,你的bundle可能會有新版本,這時候加入版本屬性會讓你的bundle的信息更加準確。
Bundle-Name: SERC Helloworld Bundle-Vendor: GR, SERC Bundle-DocURL: http://elevenframework.org Bundle-Category: example Bundle-Copyright: SERC3.2.3 代碼可見性
在JavaSE中的jar包如果放在了classpath里面,那么它對這個classpath下的所有程序都是可見的,并且這種可見性不能改變,而OSGi標準定義了如下的屬性用于描述代碼的可見性:
-
Bundle-ClassPath—它定義了形成這個bundle的所有代碼所在的位置,和Java的classath的概念相近,不同點在于,Java中的classpath是定義的jar包的位置,而這個屬性描述的是bundle內部的類在bundle中的路徑。有例子如下:
Bundle-ClassPath:.,other-classes/,embedded.jar -
Export-Package—顯式暴露需要和其他bundle共享的代碼,每個包之間用逗號分隔,每個包可用修飾詞來修飾包的其他特征。
Export-Package: org.serc.hellworld; vendor=”SERC”, org.serc.hellworld.impl; vendor=”Gou Rui” -
Import-Package—定義該bundle所依賴的外部代碼,其格式和Export-Package相同,并且也可以使用修飾詞來修飾包。不過這里的修飾詞是用來限制所依賴包的范圍的,像是一個過濾器,而不像Export-Package中用來聲明包的特征。例如如下語句:
Import-Package: org.serc.helloworld; vendor=”SERC”
4 總結
通過這一章,希望讀者們能夠對OSGi模塊層有一個初步的了解,明白這一層著重解決的是一些什么問題,并且在這之后配合著《OSGi開發環境的建立和HelloWorld》這一章可以嘗試著自己創建一個bundle,通過在MANIFEST文件中填寫元數據來暴露一些包,引入一些包。在下一篇入門篇中,我們將講解OSGi的生命周期層。
from:http://osgi.com.cn/article/7289219?
總結
以上是生活随笔為你收集整理的OSGi入门篇:模块层的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 以 OSGi 包的形式开发和部署 Web
- 下一篇: Java模块化之路 —— OSGI介绍