1. 程式人生 > >初探Java企業級開源框架OSGi(轉載)

初探Java企業級開源框架OSGi(轉載)

第一次接觸OSGi 是2006年看見的一則網上新聞,該新聞中提到BMW 汽車的通訊-娛樂(infotainment)系統採用了OSGi 架構,這套系統主要用來控制汽車上的音箱、燈光、導航和通訊等裝置,整個系統由1000多個模組組成,啟動時間卻只需要3.5秒鐘,這對於一個基於Java 的框架來講,具有兩個重大意義:一、說明了Java 執行效率並不差;二、OSGi 框架的效能尤其優秀。因此筆者對OSGi 框架產生了極大的興趣,後來終於在一個專案中負責研究和開發基於OSGi 框架的應用程式,從此對它便情有獨鍾。令人欣慰的是,OSGi 在2007年取得了諸多戰果:BEA 公司、Eclipse 基金會和Interface21 公司相繼加入OSGi 聯盟;在EclipseCon 2007大會上引起了業界的廣泛關注,其中以Spring-OSGi、Web Service 與OSGi 等技術最為引人注目,這也標誌著OSGi 將在未來與企業應用緊密結合;OSGi R4 標準釋出,相關內容被成功的寫入JSR 291 規範中;Spring 2.5 框架的釋出,宣稱其所有jar 包都相容OSGi 標準;雖然OSGi 沒能成功進入JavaEE 6 草擬計劃中,但是Sun 公司宣稱會在下一代Java EE 標準中重點考慮OSGi。因此筆者個人認為,在不久的將來OSGi 勢必會在企業應用中發揮出強大的作用,基於OSGi 框架的產品也將層出不窮。本文從OSGi 的歷史背景、OSGi 的特點、OSGi 開源框架介紹、OSGi 開發環境部署、OSGi 版的Hello World 五個部分對OSGi 框架進行概要的介紹,希望讀者能從中有所收穫。

OSGi 的歷史背景

什麼是OSGi 呢?OSGi——Open Service Gateway Initiative 字面上的意思是一個公共的服務平臺。1999年OSGi 聯盟成立,它是一個非盈利的國際組織,旨在建立一個開放的服務規範,為通過網路向裝置提供服務建立開放的標準,是開放業務閘道器的發起者。OSGi 聯盟的初始目標是構建一個在廣域網和區域網或裝置上展開業務的基礎平臺。歷史總是具有驚人的相似性,正如Java 誕生於一個嵌入式開發的專案中,卻被應用於網路平臺的開發,對OSGi 的最早設計也是針對嵌入式應用的,諸如機頂盒、服務閘道器、手機、汽車等都是其應用的主要環境。後來,由於OSGi 的諸多優秀特性(可動態改變系統行為,熱插拔的外掛體系結構,高可複用性,高效性等等),它被應用於許多PC 上的應用開發,因此逐步為開發者所知和鍾愛。現在人們對OSGi 的理解已經遠遠不是它字面和初衷所能解釋的了,筆者認為稱其為一個輕巧的、鬆耦合的、面向服務的應用程式開發框架更為確切一些。

OSGi 真正被大家所知還是和Eclipse 有密切關係的。Eclipse 很多年都是Java 開發者的首選IDE,相信只要是一個Java 開發者,應該沒有人不知道Eclipse 的。在Eclipse 3.0 以前的版本中,它本身有一套自身的外掛體系,而該外掛體系的設計非常精巧細緻,受到許多開發者的推崇,但是Eclipse 基金在Eclipse 3.0 釋出的時候,做出了一個大膽的行為,就是將Eclipse 逐步遷移到OSGi 框架中,並自己實現了一個OSGi 開源框架,取名為Equinox,該框架隨著每次Eclipse 的釋出也會相應的更新。Eclipse 之所以這麼做,其一是因為Eclipse 的外掛體系與OSGi 的設計思想不謀而合,其二也是因為OSGi 更為規範,其對外掛體系的定義也更為完整一些。事實證明Eclipse 在採用OSGi 架構後,無論從效能、可擴充套件性這兩個方面來講還是從二次開發的角度來定義,都取得巨大的成功。下圖展示了Eclipse 與OSGi 框架的關係。

OSGi 的特點

在介紹OSGi 框架的特點之前,先簡單的介紹一下OSGi 框架的各個部分,如下圖所示。

解釋一下上圖中每一層的含義,其中OS 層和JVM 層可以不用詳細介紹了,重點需要關注的是應用程式Bundles 層。框架本身提供的類載入,生命週期管理,服務註冊和規範服務也都是針對Bundles 的。每一個在OSGi 框架中執行的邏輯單元稱為一個Bundle,Bundle 實際是一個符合特定形式的jar 檔案。每一個Bundle 的功能可以是抽象的也可以是具體的。所謂抽象,就是它不是一個具體的應用,沒有完成一些業務功能,而只暴露了一些介面或者功能給其他的Bundle 使用;所謂具體,就是該Bundle 可以獨立的完成一個功能,例如連線資料庫,獲取資料等等。Bundle 有六種狀態,分別是:installed(安裝完成,本地資源成功載入),resolved(依賴關係滿足,即該Bundle 要麼是準備好運行了,要麼是已經被停止了),starting(Bundle 正在被啟動),stopping(Bundle 正在被停止),active(Bundle 被啟用,正在執行中),uninstalled(Bundle 被解除安裝了)。OSGi 有它自身的類載入機制從而控制這些載入的Bundles 彼此之間的依賴關係,而生命週期管理也是OSGi 的一大亮點,由於可動態的對這些載入的Bundles 進行安裝、解除安裝、啟動、停止等操作,所以可以動態的改變應用程式的執行狀態。當一系列的Bundles 存在於伺服器中的時候,那麼它們之間必然會存在通訊協作的部分,比如說一個通過網頁捕獲使用者輸入的Bundle 執行的時候,它必須首先需要一個Web 伺服器服務的支援,那麼這個時候服務註冊器就會從整個OSGi容器中尋找這個服務,如果能完成服務的匹配,那麼相應的功能就會很自然的實現了。OSGi 規範還規定了一組預設的服務,包括日誌、服務管理等等,這些服務在主流的開源框架中都有實現。OSGi 框架中還包括一個安全層,OSGi 的安全層擴充套件了Java 的安全機制,增並加了一些新的約束以填補了Java安全機制中的遺漏。

基於上述的介紹,讀者想必應對OSGi 有個大致的概念了,那麼接下來就讓我們來看看OSGi 究竟能夠給企業應用帶來什麼?它究竟有哪些功能值得我們把寶貴的時間投資在上面?

第一點,也是筆者認為最重要的一點,基於OSGi 的應用程式可動態更改執行狀態和行為。筆者曾經參與過開發J2EE 企業級專案,應用伺服器用的是IBM 的Websphere,主要開發基於EJB 2.1 的一些應用程式。整個開發經歷給筆者的最深印象是等待,排除編寫EJB 規範中要求的一系列繁雜的介面,單單對應用程式進行部署和測試,反覆啟動伺服器就浪費掉很多時間。而在OSGi 框架中,每一個Bundle 實際上都是可熱插拔的,因此,對一個特定的Bundle 進行修改不會影響到容器中的所有應用,執行的大部分應用還是可以照常工作。當你將修改後的Bundle 再部署上去的時候,容器從來沒有重新啟過,在外界看來,這種過程似乎從未發生過。這種可動態更改狀態的特性在一些及時性很強的系統中尤其重要,比如說一個及時銷售系統,當你的伺服器因為要更新某個元件從而花上數分鐘時間重新啟動的話,必然導致客戶的流失和利益的損失,但是採用OSGi 架構的應用則完全可以將損失降到最低。眾所周知,Spring 框架以其優秀的特性,佔據了當前企業應用開發的半邊天空,而剛剛釋出的2.5 版本,宣佈所有jar 包均支援OSGi 特性,其維護的子專案Spring-OSGi 也是專門針對Spring 與OSGi 的整合。Spring 早前版本有一點被人所詬病,就是其無法動態的改變其執行狀態,被迫停止伺服器,再修改配置檔案,而與OSGi 的結合,必然導致這種狀態的終結。最後,筆者認為這種特性也保證了系統有足夠的靈活性和可擴充套件性,對開發人員也大大節省了需要等待的時間。

第二點,它是一個穩定高效的系統。OSGi 是一個微核的系統,所謂微核是指其核心只有為數不多的幾個jar 包。基於OSGi 框架的系統可分可合,其結構的優勢性導致具體的Bundle 不至於影響到全域性,不會因為區域性的錯誤導致全域性系統的崩潰。每個Bundle 也只有當服務被呼叫的時候才會啟動,因此效能是較一般的框架高出許多。

第三點,可複用性強。OSGi 框架本身可複用性極強,很容易構建真正面向介面的程式架構,每一個Bundle 都是一個獨立可複用的單元。但是採用OSGi 框架進行企業開發是需要氣魄和勇氣的,因為當前的軟體企業,大多已經積累了許多年,都會遺留下來一些可複用的工具箱程式,而採用OSGi 架構需要重新對這些遺留系統進行封裝,更有可能的是需要把整個體系架構打散了,進行重新的架構和排列。這個開發成本不能說是不高,但筆者認為是值得的,因為從此以後企業可以利用OSGi 獨特的特性,將重複的知識輕易的過濾掉。對於新的開發,可以從企業的Bundles 庫中精簡出可複用的模組,量身定做新的Bundles,最大限度的利用了以前的積累,這樣的過程更能促使企業競爭力的增強。

OSGi 開源框架介紹

當前的OSGi 開源框架主要包含如下幾個:

Equinox

最知名,也是更新最頻繁的,由於Eclipse 基金的支援,其功能越來越完善,筆者後續的具體開發都是基於該框架來實現的。當前已釋出版本是3.3.1 與Eclipse 版本相同,實現了OSGi R4 規範,並提供很多平臺性質的服務,包括:常用功能模組、日誌模組、Web伺服器模組、Servlet 模組、JSP 解析模組等等。由於其與Eclipse 的天然聯絡,使得開發基於Equinox 的應用程式變得很簡單,筆者推薦採用此框架進行二次開發。具體內容可以從http://www.eclipse.org/equinox/ 下載。

Knopflerfish

很早的,也很優秀的一個OSGi 框架,也實現了OSGi R4 標準,去年十一月釋出了其2.0.2版本。該專案的宗旨在於建立一個易於開發的OSGi 平臺,與Equinox 不同之處在於它本身提供一些小應用例項,包括一個視覺化控制檯等,也提供基於Eclipse 的外掛。具體內容可以從http://www.knopflerfish.org/ 下載。

Felix

很新的一個OSGi 框架,社群很活躍,更新頻率高,是Apache 的開源專案。該專案2007年8月才出1.0 版,也實現了OSGi R4 規範,也提供相關的基礎服務和擴充套件服務功能。具體內容可以從http://felix.apache.org/site/index.html 下載。

OSGi 開發環境部署

講了那麼多原理,如果不動手實踐一下,總是難以令人信服的。那麼現在我們就開始動手搭建開發環境吧。

首先,你需要準備好Eclipse 筆者用的是Eclipse 3.3.1 ,還有從Equinox 網站上下載到的Equinox SDK。

其次,將Equinox SDK 解壓,解壓後是一個Eclipse 目錄,將該目錄下的所有內容拷貝至你的Eclipse 安裝目錄下,就像平時手動安裝Eclipse 外掛一樣。

最後,測試下是否安裝成功。啟動你的Eclipse,選擇Run>Open Run Dialog...在彈出的介面中,如果出現了OSGi Framework 的選項,那基本上就是成功了。點選新建一個OSGi Run方式,這時會列出一系列的載入元件,你可以檢查一下,如果裡面有org.eclipse.osgi ,org.eclipse.osgi.services 和一系列以org.eclipse.equinox 開頭的元件,那麼就真的安裝成功了。選中org.eclipse.osgi 和org.eclipse.osgi.services,點選Run 按鈕,控制檯會出現“osgi>”的提示,輸入“ss”,就會看到你執行的這兩個Bundles 的ID和狀態了。每次輸入錯誤的時候,控制檯會打印出完整的命令列表,讀者可以在此參考。

OSGi 版HelloWorld

到了真的寫一個HelloWorld 的時候了,該應用設計如下圖:


這個應用包含五個Bundles:SayHello Bundle 包含一個介面,只有唯一的方法sayHello();BobSays、RodSays、KentSays 三個Bundles 分別實現了三個具體的sayHello();而SayHelloService Bundle 提供了說hello 的機會,是具體的一個服務應用,在功能上有點類似於main 函式的味道。這個HelloWorld demo 的目的不但可以讓讀者小試牛刀,而且可以同時體會一下OSGi 最大的優點——服務狀態的可更改性。BobSays、RodSays、KentSays 實現了SayHello 暴露的介面,它們是sayHello 的具體執行者,但是在SayHelloService 呼叫的過程中,我們可以動態的改變到底是誰來說。為了實現這個demo,還需要簡單介紹一下OSGi 最簡單的實現機制:OSGi Bundles 之間包的依賴關係。每一個OSGi Bundle 的類檔案可分為私有的、引入的、暴露的三種,如下圖所示
在OSGi 中Exported Classes 是以包的方式暴露的,如圖所示,SayHello 中暴露了介面所在的包,對應的BobSays等三個Bundles 和SayHelloService Bundle 都引入了該包,這是OSGi 中最簡單的通訊方式,OSGi 規範中推薦使用面向服務的通訊方式,這裡只是舉一個簡單的例項,因此不用做的那麼複雜。

回到正題,啟動你的Eclipse,新建一個名為SayHello 的plug-in project,在Target Platform 選項中,選擇an OSGi Framework:Equinox。筆者自己設定了Activator 路徑為org.osgi.demo.sayHello.Activator,每個Activator 都具有兩個方法,start() 和 stop(),這兩個方法是該bundle 啟動、停止的時候,呼叫的方法,通常在這裡註冊、初始化或登出該Bundle 服務的過程,這裡不需要更改任何Activator 中的內容,用系統自動生成的就可以了。在建立好專案後,會出現對SayHello 專案的配置,這裡可以通過dependencies 選項卡,設定需要的plug-in 和引入的package;可以通過runtime 選項卡的設定,確定暴露哪些包。我們新建一個org.osgi.demo.say 包,並建立SayHello 介面,只有一個返回void的方法sayHello() ,並將此包設為暴露的。這些設定都儲存在專案的META-INF目錄下的MANIFEST.MF檔案中,以後要更改的話,只需開啟該檔案即可。 SayHello 介面的程式碼如下:
public interface SayHello {
            public void sayHello();
            }
            

同樣類似的新建一個名為BobSays 的plug-in project。筆者設定的包為org.osgi.demo.bob,這裡需要在配置dependencies 的時候,將包org.osgi.demo.say 引入。建立新的類BobSays,程式碼如下:

public class BobSays implements SayHello {
            public void sayHello() {
            System.out.println("Bob says /"Hello OSGi world/"");
            }
            }

這裡需要覆寫在BobSays Bundle 中的Activator 的兩個方法,具體程式碼如下:

public class Activator implements BundleActivator {
            private ServiceRegistration serviceReg = null;
            public void start(BundleContext context) throws Exception {
            serviceReg = context.registerService(SayHello.class.getName(),
            new BobSays(), null);// 1
            }
            public void stop(BundleContext context) throws Exception {
            if (serviceReg != null)
            serviceReg.unregister();// 2
            }
            }
            

完成的主要功能是:1、在啟動服務的時候,註冊BoySays 服務為一個SayHello 服務;2、在停止服務的時候,從上下文中解除安裝該服務。

類似的建立KentSays、RodSays 兩個project。

最後,建立一個名為SayHelloService 的plug-in project。筆者設定的包為org.osgi.demo.service,同樣在配置dependencies 的時候,將包org.osgi.demo.say 引入。建立SayHelloService類,程式碼如下:

public class SayHelloService {
            private SayHello say;
            public void helloWorld() {
            for (int i = 0; i < 10; i++) {
            try {
            Thread.sleep(1000);
            } catch (InterruptedException e) {
            e.printStackTrace();
            System.err.println("Thread can't sleep");
            }
            say.sayHello();
            }
            }
            public void setSay(SayHello say) {
            this.say = say;
            }
            }
            

這裡採用依賴注入的方式,所以有一個setSay() 方法,來設定一個具體的SayHello。helloWorld() 方法就是呼叫特定的SayHello.sayHello() 來完成的,用10秒鐘的時間列印十次sayHello() 的具體內容。該Bundle 的Activator 程式碼如下:

public class Activator implements BundleActivator {
            private ServiceRegistration serviceReg = null;
            public void start(BundleContext context) throws Exception {
            SayHelloService sayService = new SayHelloService();
            serviceReg = context.registerService(SayHelloService.class.getName(),
            sayService, null);// 1
            ServiceReference serviceRef = context
            .getServiceReference(SayHello.class.getName());// 2
            sayService.setSay((SayHello) context.getService(serviceRef));// 2
            sayService.helloWorld();// 3
            }
            public void stop(BundleContext context) throws Exception {
            if (serviceReg != null)
            serviceReg.unregister();
            }
            }
            

完成的主要功能是:1、註冊SayHelloService 服務;2、獲取一個的SayHello 服務;3、並注入到SayHelloService 服務中,現在注入的服務是從服務上下文中具體獲取的,而到底是哪個,只有在執行時狀態才能決定。

至此,所有的Bundles 我們都已經完成了,選擇Open Run Dialog...,並選中上述五個Bundles 和OSGi 核心Bundle,點選Run 按鈕。輸入“ss”,列出了6個Bundles 的狀態,此時,如果你的SayHelloService Bundle 狀態是Resolved,那麼你可以通過命令“start ‘SayHelloService Bundle 狀態的id’”,啟動SayHelloService,此時你會看到打印出的10條hello world資訊。讀者可以手動利用用命令“start” 和“stop” 改變sayHello 的具體執行者,動態的更換實際sayHello 的執行者。這個簡單的HelloWorld 應用,可以說明SayHelloService 在具體執行的過程中行為是可動態改變的,並且改變只是區域性的。

小結

讀完本文,實際動手做過HelloWorld,想必讀者對OSGi 框架也應該有所瞭解了,OSGi 框架在國外關注率是挺高的,但是在國內的推廣和使用卻不夠廣泛,可能是因為OSGi 字面上的意思太過於抽象,因此筆者在這裡將這個優秀的框架介紹給大家,本片只是一個簡單的介紹,並不涉及OSGi 框架深入的知識。