軟體的模組越來越外掛程式化發展了,連硬體都處處熱插拔,軟體更當如此。記原來有個 JPF(Java Plugin Framework),也能實現動態外掛程式化,但要是有個業界標準的東西一般來說會更好的。於是輪到 OSGI(Open Service Gateway Initiative) 登台,OSGI 出來也有好幾個年頭了,應用也轟轟烈烈的,比如 Eclipse 3 開始不再使用原來的外掛程式體系,完全用 OSGI 搭建。WebSphere 6.1 也全面改用 OSGI;JBoss、WebLogic、Spring DM,甚至是 BMW 車的控制系統中都得到了很好的應用。
前面講到可以用 OSGI 作為你的微核心,微核心的好處可以打個這樣的比方:一台 Linux 伺服器出故障了,應用程式壞了、某些服務不能訪問等,但只要它還來連入網路,SSH 還是活的,我們就有辦法進去修複它,想安裝、卸載什麼都行。
OSGI 也能讓你動態增減服務,或者說動態載入卸載類等資源。OSGI 中的資源稱作 Bundle,所以如果是基於 OSGI 的程式,能夠在網上找到許多有用的 Bundle 直接線上插入到你的軟體系統中。Bundle 其實就是一個 jar 檔案,只是在 MANIFEST.MF 中有些特殊的定義,每個 Bundle 靠實現的 BundleActivator 的去控制 Bundle 的生命週期和發布、監聽架構的事件,或者說與架構進行通訊。
OSGI 是個規範,它的實現目前有 Equinox、Knopflerfish、Oscar、Felix 等,由於天天與我打交道的是 Eclipse 以及它背後的 Equinox,所以這裡的樣本程式就使用 Equinox 吧。
本例是基於 《OSGI實戰》這一 PDF 文檔稍加改造而成,比原例要簡單,因為盡量避免了引入其他的 Bundle,不是 Web 應用,而是做成了一個視窗程序。
一樣的,也是個使用者登陸驗證程式,程式運行中可以動態切換驗證方式(資料庫驗證或 Ldap 驗證),也可以插入新的驗證方式,如檔案驗證方式。基於此,我們需要建立四個 Bundle,分別為:
1. UserValidatorBundle -- 定義了 Validator 介面方法,此例中還作為與使用者互動的 Bundle
2. DbValidatorBundle -- 實現了 Validator 方法,用資料庫進行驗證的 Bundle
3. LdapValidatorBundle -- 實現了 Validator 方法,用 Ldap 進行驗證的 Bundle
4. FileValidatorBundle -- 實現了 Validator 方法,用檔案進行驗證的 Bundle
定義介面的 Bundle 需要匯出定義介面的 package,而實現介面定義的 Bundle 需要匯入前面匯出的介面 package。
OSGI 有點像 SPI(Service Provider Interface) 模式,上層有個介面定義,然後有多幾可能的實現提供者,運行時可以選擇哪種實現,例如 common-logging 與 log4j, jdk log 那樣的關係,但是 SPI 不具有 OSGI 的動態性。同樣,在 OSGI 的某個 Bundle 中也需要定義一個服務介面,沒有介面規約就沒法指導方法如何去調用。映射到 SPI 模式就是,UserValidatorBundle 為服務定義者,其他 Bundle 為服務提供者。
在 Eclipse 中直接能開發 OSGI 的 Bundle,並支援可視化操作與調試。因為 Eclipse 本身就是用的 Equinox,所以不需要單獨下載 Equinox SDK。只要你電腦上有 Eclipse,馬上就可以窺探一下 Equinox 的模樣。
Eclipse 中菜單 Run ->Run Configurations 視窗中,左邊 OSGi Framework 中,右鍵 new,然後 Deselect All,只勾選幾個 Bundle,點擊右下角的 Run 按鈕就行啦。
這時候會在 Eclipse 的 Console 視窗中出現一個 osgi>提示符,在其中可以輸入各種命令,如 ss 顯示所有的 Bundle,如圖:
也可以安裝卸載 Bundle、啟用停止 Bundle 等等。當然實際做出來的 OSGI 程式不會去帶那麼一個 osgi 控制台的,osgi 控制台下的操作都可以在你的程式中完成,或者作為一個真正的後台管理介面,使用者是不會覺察到的。之所以放個 osgi>出來,是為了開發、測試方便。
我們現在要做的就是像 osgi>控制台下用 ss 命令顯示出來的那種 Bundle。本例中所用的 Eclipse 是 3.5.2 版,其他 3.x 的版本可能會有細微的差異,應該不會有影響的。
正式開始動手
一. 建立 Plug-in Project 工程 UserValidatorBundle
填入工程名稱 UserValidatorBundle,This plug-in is targeted to run with: 指定為 an OSGI framework standard,建立一個標準的 OSGI 工程。
輸入Bundle的相關中繼資料資訊,這面這些資訊會反應在 META-INF/MANIFEST.MF 檔案中。
Plug-in ID指的是Bundle的唯一標識,在實際的項目中可以採用類似java的包名組織策略來保證標識的唯一性;
Plug-in Version指的是Bundle的版本;
Plug-in Name指的是Bundle的更具有意義的名稱;
Plug-in Provider指的是Bundle的供應商;
還要建立一個關鍵的 Activator 類了,要好好考量一下包名稱。圖中顯示說這個是 Options(可選的),而其實在我們的例子中,還是是很重的,用來啟動介面或註冊服務。
不需要 <Next>選擇模板的話,直接點接 <Finish>按鈕就行了。Eclipse 會進入到 Plug-in Development 視圖,並用 Plug-in Manifest Editor 開啟 MANIFEST.MF 檔案。也為你產生了前面指定的 Activator.java 檔案。如下圖:
二. 對外提供使用者驗證介面包
1) 建立一個 Validator 介面
2) 設定要匯出的對外提供服務的 package
在 MANIFEST.MF 編輯器中,選擇 Runtime 標籤,Exported Packages 中點擊 Add 按鈕,在彈出的視窗中選擇 Validator 介面所在的 package com.unmi.login.activator。
這步操作,其實也就是在 MANIFEST.MF 中添加了一行
Export-Package: com.unmi.login.service
好了,建立 UserValidatorBundle 的工作暫時就告一段落了。
三. 建立其他幾個 Bundle
按照建立 UserValidatorBundle 相類似的方法,建立其他幾個 Bundle,注意選擇好每個 Bundle 的 Activator 實作類別的位置。
1) 匯入服務介面 package
另外建立好了 DbValidatorBundle、LdapValidatorBundle、FileValidatorBundle 之後,需要為它們匯入 UserValidatorBundle 匯出的 package,以明示自己是 Validator 介面的提供者。
操作方法是,各自的 MANIFEST.MF 編輯器的 Dependencies 標籤頁中,Imported Packages 裡點 Add 按鈕,快顯視窗中選擇 com.unmi.login.service 包,其實就是為編程指定了依賴的包。
這步會在 MANEFEST.MF 中產生一行:
Import-Package: com.unmi.login.service
2) 編寫實現 Validator 介面的實作類別
因為是示範,所以驗證過程寫入程式碼,比如在 LdapValidatorBundle 中的 Validator 介面的實作類別 com.unmi.login.service.impl/LdapValidatorImpl.java 代碼為
其他幾個 Bundle 中的 Validator 實作類別的代碼也是類似。
3) 實現 Bundle 的 BundleActivator 介面
前面講過,Bundle 的 BundleActivator 實現是用來管理自身的生命週期和與架構互動的,所以雖要在各自的 BundleActivator 實現中,當啟動 Bundle 時,註冊自己以讓架構能尋找到該 Bundle, 停止 Bundle 時,把自己從架構中登出掉,以釋放相關資源。先來編寫那幾個服務提供者的 BundleActivator 實現,如 LdapValidatorBundle 的類 com.unmi.login.activator.Activator.java 的代碼如下:
注意其中的代碼是怎麼向 BundleContext 中註冊自己的執行個體的,以及如何卸載,瞭解 BundleContext.registerService() 方法各參數的意義。
其他幾個 Bundle,如 FileValidatorBundle 和 DbValidatorBundle 的 BundleActivator 實現代碼也是類似,請仿照之。但是 UserValidatorBundle 的 Activator 類的實現就不太一樣了,因為 UserValidatorBundle 是個服務定義者,而且這裡也拿它來作為開啟程式介面的入口,所以它的 Activator 實現的 start() 方法中就會安排安多做些事情的,也就單獨拿出來說明。
4) 編寫 UserValidatorBundle 的 Activator
在該 Activator 的 start() 方法中會彈出一個登陸視窗,讓使用者輸入使用者名稱和密碼,然後登陸。停止該 Bundle 時會把登陸視窗關閉掉。注意,因為其他的 Bundle 不需要在上下文中尋找本 Bundle 的執行個體來調用,所以在 start() 中沒有向 BundleContext 註冊自身的代碼。
這裡注意了,我們運行Bunble裡,將選擇要啟動並執行bunble,被選擇的bunbles都會從繼承BundleActivator類的子類的start方法裡執行,所以我們想要加在啟動一個bunble時實現什麼功能,我們只要寫入到start方法裡即可,如這裡將寫好的驗證表單放入到start方法裡,那麼一運行就會彈出驗證表單。
5) 調用服務的代碼
完成在架構中尋找可用的服務,如 Validator 實作類別的執行個體,然後調用實作類別的驗證方法。這些代碼實現在 LoginWindow 中,在 LoginWindow.java 中代碼較多,完整代碼可查閱文後附件,此處只列出關鍵性代碼,即登陸按鈕點擊後所做的事情
到現在為止,所有的代碼編寫完畢,馬上就可以在 Eclipse 中運行了。
四. 運行示範
示範之前,先對程式的行為進行簡要的說明。程式運行後會跳出一個使用者登陸視窗,在視窗中輸入使用者名稱和密碼,點登陸按鈕便能調用實際的驗證實作類別來驗證使用者,同時在視窗上可以看到登陸是成功,還是失敗,以及失敗時的原因為何,以登陸按鈕旁邊用個小字母標明當前是用的什麼方式驗證的。如圖:
登陸按鈕旁邊用個字母標識了當前的驗證方式,即架構中哪個 Bundle 在提供驗證服務。N 代表未知,L 代表 LdapValidatorBundle,D 代表 DbValidatorBundle,F 代表 FileValidatorBundle。
1) 建立回合組態
Eclipse 的菜單 Run ->Run Configurations,OSGi Framework 上右鍵,new 一個 UserValidator 回合組態,在右邊 Bundles 中選上我們建立的那四個 Bundle,可以分別設定它們的 Start Level 和 Auto-Start,例如這裡設定 FileValidatorBundle 的 Auto-Start 為 false。在該頁右下角可以直接點擊運行。
啟動後,出現 osgi 控制台,列印出 LdapValidatorBundle 和 DbValidatorBundle 啟動的資訊,並顯示了登陸視窗。
現在就可以在 osgi>控制台執行些操作來觀察這個程式的運行狀態,比如首先輸入 ss 命令,看到載入了我們需要的 Bundle,而 FileValidatorBundle 是 RESOLVED,而不是 ACTIVE,那是因為我們建立回合組態的時候把該 Bundle 的 Auto-Start 設定成了 false。
osgi>控制台下的基本操作有
ss 顯示所有已載入的 Bundle,我們可以不時的執行這個指令,看看有哪些 Bundle 在
stop <id>停止指定 id 的 Bundle,會觸發該 Bundle 的 Activator 實現的 stop() 方法
start <id>啟動指定 id 的 Bundle,會觸發該 Bundle 的 Activator 實現的 start() 方法
install <url>安裝一下 Bundle,指定 Bundle 的 jar 檔案 url
uninstall <id>卸載指定 id 的 Bundle,必須重新 install 後才能使用
還有很多命令,這裡不詳述,在 osgi>提示符下亂輸個它不認識的指令(如 dd),osgi>便會提示出協助來。
2) 多方示範
(1) 直接登陸時的情形
osgi>控制台下先看下有哪些 Bundle 及狀態,然後在登陸視窗中輸入 Unmi/1234,登陸到:
登陸按鈕旁邊顯示 L,控制台中顯示 "使用 Ldap 進行登陸驗證" 說明是用的 LdapValidatorBundle 驗證的。
(2) 停掉 LdapValidatorBundle 看看
在 osgi>控制台下執行 stop 1,再點擊登陸視窗的登陸按鈕,我們看到:
登陸按鈕旁邊顯示 D,控制台中顯示 "使用資料進行登陸驗證" 說明是用的 DbValidatorBundle 驗證的。
(3) 把 DbValidatorBundle 也停了
在 osgi>控制台下執行 stop 3,再點擊登陸視窗的登陸按鈕,我們看到:
發現,現在沒有活動的 Validator 實現 Bundle,所以無法登陸。
(4) 把 FileValidatorBundle 啟起來
在 osgi>控制台下執行 start 4,這時候把密碼多加個 5, 再點擊登陸視窗的登陸按鈕,我們看到:
沒問題,可以使用剛啟用 FileValidatorBundle 來進行登陸驗證了。
你還可以再次把沉睡的 LdapValidatorBundle 或 DbValidatorBundle 啟動起來,或者乾脆把某個 Bundle uninstall 掉,用 ss 都看不到了,要用的話必須 install 回來。
五. 發布基於 OSGI 的系統
前面都是在 Eclipse 環境中啟動並執行基於 OSGI 程式,那我們離開 Eclipse 該怎麼運行呢。我們也沒有看到所謂 Bundle 即是一個 jar 那樣的檔案,寫好的程式,最後的事情就是發布了。
1) 匯出 Bundle 項目為 jar 包
Eclipse 菜單 File ->Exports,快顯視窗中選擇 Deployable plug-ins and fragments
選擇那四個 Bundle,設定輸出檔案的目錄為 E:workspaceOsgiDemo,然後點 Finish 按鈕
完成後,在 E:workspaceOsgiDemo 檔案夾中,產生了一個 plugins 目錄,裡面就是剛剛匯出的那幾個 Bundle。
DbValidatorBundle_1.0.0.201003301134.jar
FileValidatorBundle_1.0.0.201003301134.jar
LdapValidatorBundle_1.0.0.201003301134.jar
UserValidatorBundle_1.0.0.201003301134.jar
2) 拷入所需的支援包
從 Eclipse 的 plugins 目錄中拷貝 org.eclipse.osgi_3.5.2.R35x_v20100126.jar 到 E:workspaceOsgiDemo 目錄中。
3) 配置 config.ini
先要在 E:workspaceOsgiDemo 目錄中建立子目錄 configuration,然後在其中建立檔案 config.ini,內容為(注意osgi.bundles的值是按按我們實際打包出去的bunble名字來改它的值):
osgi.noShutdown=true
#避免Unable to acquire application service. Ensure that the org.eclipse.core.runtime錯誤
eclipse.ignoreApp=true
#因為使用了 swing,無該屬性則報 java.lang.NoClassDefFoundError: javax/swing/JFrame
org.osgi.framework.bootdelegation=*
#這裡有意沒有載入 FileValidatorBundle,待以後 install
osgi.bundles=plugins/DbValidatorBundle_1.0.0.201003301134.jar@start,
plugins/LdapValidatorBundle_1.0.0.201003301134.jar@start,
plugins/UserValidatorBundle_1.0.0.201003301134.jar@start
osgi.bundles.defaultStartLevel=4
4) 建立批處理 run.bat
在 E:workspaceOsgiDemo 中建立批次檔 run.bat,內容為(這裡也要注意一點就是你的org.eclipse.osgi版本不同下面那個版本時,你需要改為你的實際版本):
@echo off
java -jar org.eclipse.osgi_3.5.2.R35x_v20100126.jar -console
E:workspaceOsgiDemo 中的檔案目錄結構如下:
E:WORKSPACEOSGIDEMO
│ org.eclipse.osgi_3.5.2.R35x_v20100126.jar
│ run.bat
│
├─configuration
│ config.ini
│
└─plugins
DbValidatorBundle_1.0.0.201003301134.jar
FileValidatorBundle_1.0.0.201003301134.jar
LdapValidatorBundle_1.0.0.201003301134.jar
UserValidatorBundle_1.0.0.201003301134.jar
5) 運行 run.bat
雙擊 run.bat 即可,這時候也出現 osgi>控制台和一個登陸視窗,不過這時候是在 dos 視窗中的。你可以不讓程式出現 dos 視窗,org.eclipse.osgi_3.5.2.R35x_v20100126.jar 是個可執行檔 jar,又擊它正常情況下就只會顯示一個登陸視窗。你也可以去下載個 Equinox 程式的 Launcher 來啟動 OSGI 程式。不過我們這裡需 osgi>控制台,所以要保留那個 dos 視窗,如下圖:
剛啟動時我們沒有載入 FileValidatorBundle,我們執行下列命令
osgi>install reference:file:plugins/FileValidatorBundle_1.0.0.201003301134 #載入 FileValidatorBundle
osgi>stop 1 2 #停掉 DbValidatorBundle 和 LdapValidatorBundle
osgi>start 4 #啟動 FileValidatorBundle
然後輸入使用者名稱和密碼試下,從控制台輸出和登陸按鈕旁邊的 F 標誌,確實是使用了新安裝的 FileValidatorBundle。
用 install <url>你可以隨時安裝任何地方的 Bundle jar 包。
執行 stop 1 2 只是停掉 DbValidatorBundle 和 LdapValidatorBundle,並沒有從 JVM 中卸載掉,用 ss 命令可以看到它們處於 RESOLVED 狀態。這時候你也可以試著去 E:workspaceOsgiDemoplugins 中刪除那兩個檔案 DbValidatorBundle_1.0.0.201003301134.jar 和 LdapValidatorBundle_1.0.0.201003301134.jar,系統會提示正在使用無法刪除。這時候再執行下卸載命令
osgi>uninstall 1 2
再去刪除那兩個文是可以成功的,說明 JVM 真的卸載掉了這兩個包。可以看出,OSGI 在通過 install 和 uninstall 動態安裝和卸載 Bundle 是多麼的方便。
比如說原有系統中要添加一個功能模組,只要動態安裝到架構中就能即使用了。原有系統中有個模組有 Bug,修改好,卸載再重裝一下就 OK 啦,基本上系統類別似於這種變動都不需要停掉服務,重啟應用。
還是前面那句話,實際應用中是一般不會有這種 osgi>控制台的,你想要在 osgi>控制台下來做那些動人的操作也行。因為 Equinox 是純 java 實現的,所以能在 osgi>控制台下執行的命令,一定可以在你的程式裡進行控制,或者以一種對於後台人員更友好的管理方式。
上面的例子只是引領你對 OSGI 有個基本的印象,進而真正領悟到 OSGI 的先進思想,這樣才能在你的實際工作運用自如。當然我們在項目中不能因為技術而技術,如果是小項目我想大可不必把 OSGI 揉進來。具有一定規模的項目可考慮用 OSGI 架構去站在很高的層次去做個微核心,並且要合理的進行組件規劃,才能靈活應動組件的動態插拔。