Open Service Gateway Initiative(OSGi) 是一個針對 Java 動態模塊開發的規範。基於中間件的 OSGi 技術提供了一個面向服務,基於組件的開發環境,並提供標準化的方式來管理整個軟件生命週期。OSGi 爲那些須要長時間運行,動態更新而且對運行環境的影響儘量小的系統提供了很好的支持。基於 OSGi 開發的系統具備複雜度低、可重用、動態部署、可持續運行、開發簡單等特色。html
OSGi 技術結合了上述特色的各方面來定義一個動態服務部署框架,能夠進行遠程管理。OSGi 技術起初只是關注於嵌入式領域,諸如機頂盒、服務網關、手機等應用環境。可是它完美地適用在任一模塊化、面向組件、面向服務的項目。Eclipse V3.0 之後採用 OSGi 做爲其模塊化和動態化平臺,設計了 Equinox 內核,使用 OSGi 技術幫助其進行類載入,大大提高了 Eclipse 的啓動速度。在應用服務器上,WebSphere,Weblogic,JBOSS 等著名的服務器都支持或使用了 OSGi 技術。java
Felix 是一個 Apache 旗下 OSGi 實現的開源框架,它的最終的目標是提供一個徹底兼容的 OSGi 框架和標準服務的實現。Felix 當前實現了 OSGi 規範 4 的大部份內容,目前 Felix 提供的 OSGi 框架功能是很是穩定的。web
採用 Spring DM 和 Jetty 等 Web 容器開發基於 OSGi 的 Web 應用的方法已經在不少書本或技術文章上說起。可是這種開發方法與傳統的 Web 開發差異較大,開發人員很難轉換到這種開發模式上,而且它的穩定性也沒有獲得充分的驗證。spring
不少 Web 開發都採用 Struts 做爲其控制層,很幸運的是,最新發布的 Struts2.1.8.1 中,加入了對 Felix OSGi 的支持,可以在傳統的 Web 開發中集成 OSGi 的模塊管理平臺,並且開發方法沒有太大的改變,開發後的應用程序仍像原先同樣能夠方便的部署在 Tomcat,JBoss 等容器上。shell
本文將經過下面的示例,詳細講述如何使用 Felix 和 Struts 開發 Web 應用。apache
回頁首瀏覽器
下面講解的示例是一個獲取時間信息並在 Web 瀏覽器中顯示的簡單示例,該示例主要介紹了怎樣使用 Felix 和 Struts 結合起來開發 Web 應用。該示例中有兩個獲取時間信息的 bundle,這兩個 bundle 實現同一個接口服務,可是有不一樣的實現,這兩個 bundle 能夠在應用中動態部署。經過該示例,能夠體現出基於 OSGi 開發的項目具備良好的模塊化以及 OSGi 的 bundle 動態部署的能力,從而說明了 OSGi 適用於開發持續運行且須要動態更新的系統。服務器
在這個示例中,一共包括五個工程,一個 Web Application 工程和四個 OSGi bundle 工程。Web Application 工程是用於 Web 部署。四個 OSGi bundle 中,包括一個 Web bundle,用於 Web 交互;一個 time service bundle,包含一個獲取時間信息的接口服務;一個 local time service bundle,實現接口服務,用於獲取本地時間信息;一個 utc time service bundle,用於獲取世界標準時間(Universal Time Coordinated,UTC)信息。app
本示例的結構原理如圖 1 所示。在 Web Container 中註冊了 Struts OSGi 的監聽器,該監聽器會去啓動或中止 Apache Felix Host,Apache Felix Host 是 Struts OSGi Plugin 和 Felix Framework 的鏈接點。Felix Host 會去建立和初始化 Felix Framework,Felix Framework 負責管理系統中的其他的全部 bundle,Struts OSGi Plugin 會監聽 bundle 的變化,若是發生變化的 bundle 是一個 Struts bundle, 則會去加載 Struts 的配置。框架
圖 1. 示例結構原理圖
本文示例使用的 Web 開發環境包括以下組件,部分框架能夠 參考資料中下載。
Web Application 工程的建立方式與一般的 Web 工程相似,可是須要加入 Felix 的支持和 Struts2 OSGi Plugin. Felix 是 OSGi 的平臺,用於管理整個系統中的全部的 bundle,而 Struts2 OSGi Plugin 是 Struts2 和 OSGi 鏈接的橋樑,經過 Struts2 OSGi Plugin 將 Felix 融入到 Struts2 框架中。另外,還須要加入 Struts2 OSGi Admin bundle,這個 bundle 向管理人員提供基於 Web 的管理 OSGi 平臺中的 bundle 的操做入口。同時在 web.xml 中須要加入 Struts OSGi 監聽器,這樣 OSGi 平臺中的 bundle 發生變化時,會觸發該監聽器去作一些與 Struts 相關測操做,例如增長 Action 或使 Action 失效。
web.xml 中過濾器和監聽器部分的配置內容如清單 1:
清單 1. web.xml 過濾器和監聽器配置
<filter> <filter-name>struts2-prepare</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareFilter</filter-class> </filter> <filter> <filter-name>struts2-execute</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsExecuteFilter </filter-class> </filter> <filter-mapping> <filter-name>struts2-prepare</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>struts2-execute</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <listener> <listener-class>org.apache.struts2.osgi.StrutsOsgiListener</listener-class> </listener> <listener> <listener-class>org.apache.struts2.dispatcher.ng.listener.StrutsListener </listener-class> </listener>
Web Application 工程的目錄結構如圖 2 所示:
圖 2. Web Application 工程的目錄結構
將 Web Application 部署到 Tomcat 上並啓動 Tomcat,而後在瀏覽器中輸入 http://localhost:8080/webapp/osgi/admin/bundles.action, (webapp 是項目部署到 Tomcat 中的名字 ) 若是看到了相似於 圖 5的 bundles 列表,說明 OSGi 環境配置成功。
消息接口服務 bundle 是提供消息服務的接口,該接口將被 Web bundle 所使用,其餘 bundle 能夠不一樣的形式實現該接口。在這裏利用 eclipse 新建插件工程的功能來建立 OSGi bundle。須要特別設置 an OSGI framework 爲 standard 方式,這種方式容許部署項目到標準的 OSGI 容器中。新建 OSGi 工程的嚮導如圖 3 所示。
圖 3. 新建 OSGi 工程嚮導圖
在該項目中開發一個用於獲取時間信息的接口,經過該接口能夠獲取字符串形式的時間信息。
清單 2. 獲取時間服務接口代碼
package com.example.time.service; public interface TimeService{ public String getTime(); }
須要將該 bundle
中的服務包的類和接口就暴露給了其餘的 bundle
,其餘的 bundle
能夠經過 import
這個包來使用其中的類和接口。
獲取本地時間消息服務 bundle 實現了時間消息接口服務。在該 bundle 種返回的時間消息是當前時區的時間信息。由於用到了接口服務包,因此須要在 Import-Package 中加入接口服務包。
清單 3. 獲取本地時間實現代碼
package com.example.time.local.service; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import com.example.time.service.TimeService; public class LocalTimeService implements TimeService{ @Override public String getTime(){ Calendar calendar = Calendar.getInstance(); Date date = calendar.getTime(); SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return "The local time:" + formatter.format(date); } }
OSGi bundle 中的服務要可以被其餘 bundle 使用,使用將服務發佈出來。在該 bundle 的 Activator 的 start() 方法中註冊該服務,能夠發佈這個服務。當這個 bundle 啓動時,將獲取本地時間發佈爲一個服務。服務發佈的代碼如清單 4 所示。
清單 4. 服務發佈
public void start(BundleContext context) throws Exception{ context.registerService(TimeService.class.getName(), new LocalTimeService(), null); }
獲取 UTC 時間消息實現服務一樣實現了時間消息接口服務,該 bundle 主要是用於和上一個 bundle 即獲取本地時間消息服務進行動態的替換,用於表現 OSGi 的動態部署的能力。
清單 5. 獲取 UTC 時間服務實現
public class UTCTimeService implements TimeService { @Override public String getTime() { Calendar calendar = Calendar.getInstance(); int zoneOffset = calendar.get(Calendar.ZONE_OFFSET); int dstOffset = calendar.get(Calendar.DST_OFFSET); calendar.add(Calendar.MILLISECOND, -(zoneOffset + dstOffset)); Date date = calendar.getTime(); SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm s"); return "The UTC time:" + formatter.format(date); } }
Web bundle 是系統 Web 交互的入口。在該 bundle 中須要使用時間消息接口服務 bundle 中的接口,全部須要在 MANIFEST.MF 的 Import-Package 加入時間消息接口服務包,另外,爲了可以識別出該 bundle 是一個 Struts bundle, 須要將該 bundle 設置爲可被 Struts2 支持 , 即在 MANIFEST.MF 中加入 Struts2-Enabled: true, 這樣該 bundle 中的 struts.xml 就會被加載。最終的 MANIFEST.MF 的配置如清單 6。
清單 6. Web Bundle 的 MANIFEST.MF 配置
Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: com-example-time-web Bundle-SymbolicName: com.example.time.web Bundle-Version: 1.0.0.qualifier Bundle-Vendor: keki Bundle-RequiredExecutionEnvironment: JavaSE-1.6 Struts2-Enabled: true Import-Package: com.example.time.service, com.opensymphony.xwork2, org.apache.struts2.osgi.interceptor, org.osgi.framework;version="1.3.0"
爲了實現用戶交互,還須要建立一個獲取時間消息響應的 action。該 Action 的 execute() 方法代碼如清單 7 所示。
清單 7. Action 實現方法
public String execute(){ ServiceReference ref = bundleContext.getServiceReference( TimeService.class.getName()); TimeService timeService = (TimeService) bundleContext.getService(ref); timeMessage = timeService.getTime(); return SUCCESS; }
這個 Web bundle 中獨立的創建一個 struts.xml,這個 struts.xml 將會爲單獨加載,須要注意的是 Struts 的 pacakge 繼承 osgi-default 這個包,osgi-default 已在 struts2-osgi-plugin 這個 jar 包裏面定義。
清單 8. Web Bundle struts.xml 的 action 定義
<struts> <package name="time-example" namespace="/time" extends="osgi-default"> <action name="time" class="com.example.time.web.action.TimeAction"> <result type="freemarker">time.ftl</result> </action> </package> </struts>
將開發好的四個 bundle 導出成 plugin 的包,並將它們放在 Web App 工程中 ,bundles 的目錄結構如圖 4 所示。
圖 4. Web Application 中的 bundles 目錄結構
啓動 Tomcat,在瀏覽器地址欄輸入 http://localhost:8080/webapp/osgi/admin/bundles.do, 能夠看到所繫統中全部的 bundle 的列表。
圖 5. 部署的 bundles 列表
在瀏覽器地址欄輸入 http://localhost:8080/webapp/time/time.do,能夠得到時間信息,此時的時間信息爲本地時間信息,當前 TimeService 這個服務有 local time service 和 UTC time service 兩個實現,調用的是 local time service 這個實現。
圖 6. 獲取本地時間頁面顯示
此時,在瀏覽器地址欄輸入 http://localhost:8080/webapp/osgi/admin/shell.do,而後輸入命令 stop 1, 將 Local time service 這個 bundle 中止掉,輸入命令 ps, 能夠看到 local time service 這個 bundle 的 state 已經變爲 Resolved.
圖 7. OSGi Shell 管理頁面
在瀏覽器地址欄再次輸入 http://localhost:8080/webapp/time/time.do 獲得的結果如圖 7 所示。
圖 8. 獲取 UTC 時間顯示頁面
經過上面的演示,咱們能夠看到 OSGi bundle 的動態部署能力。
經過 Felix 能夠方便的管理項目中的 bundle,而且實現 bundle 的熱部署,即插即用,即刪即無的特性,特別適用於可持續運行的系統。
輸入命令 install <bundle-url>,而後輸入 start <bundle-id> 便可。如 $install file:/k:/plugins/com.example.time.local_1.0.0.qualifier.jar , $start 7
輸入命令 update <bundle-id> <bundle-url> 便可。如
$ update 1 file:/k:/plugins/com.example.time.local_1.0.0.qualifier.jar
輸入命令 start <bundle-id> 啓動 bundle;輸入命令 stop <bundle-id> 中止 bundle。如
$ start 2 , $ stop 1
若 bundle 處於 Installed 或 Resolve 狀態,則直接輸入命令 uninstall <bundle-id>。若 bundle 處於 Actived 狀態,則先輸入命令 stop <bundle-id> 中止 bundle, 再輸入命令 uninstall <bundle-id>。如 $ uninstall 1
在上面的示例中,bundle 中最大的啓動級別只能爲 3。若是在 bundles 下面增長一個目錄 4,即 bundles/4,則目錄 4 中的 bundle 是沒法啓動的,而在不少時候,特別是在大型的項目中,最大啓動級別爲 3 是不能知足要求,此時能夠 web.xml 中添加啓動級別的參數。以下面把最大啓動級別設置爲 5。
清單 9. 啓動級別配置
<context-param> <param-name>struts.osgi.runLevel</param-name> <param-value>5</param-value> </context-param>
Struts.xml 的頭部有 Struts Configuration DTD 的引用定義,通常 DTD 文檔的 URL 爲 http://struts.apache.org/dtds/XXX.dtd ,示例以下所示:
清單 10. struts.xml 頭部 dtd
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd">
若是沒法鏈接上 http://struts.apache.org/,那麼在加載 Struts 的 bundle 時也將會出錯,由於 bundle 與 Web Application 的 lib 的加載路徑不一致,沒法從 Web Application 的 lib 下面找到 XXX.dtd 文件。此時能夠經過修改 dtd 文件的 URL 來解決,能夠改爲一個本地文件系統的 URI,如 file:/c:/webapp/dtds/struts-2.0.dtd,也能夠改成本地的 Web 服務器或一個能夠鏈接上的服務器的 URL,如 http://localhost/dtds/struts-2.0.dtd。
Spring DM 使得 Spring 和 OSGi 成爲可能,在本文的開發環境中,也能夠加入 Spring DM 來管理系統中的對象。首先加入 Spring DM 必要的 jar 包,如
清單 11. Spring 依賴包示例
com.springsource.org.aopalliance-1.0.0.jar, com.springsource.org.apache.commons.logging-1.1.1.jar, spring-aop-2.5.5.jar, spring-beans-2.5.5.jar, spring-context-support-2.5.5.jar, spring-core-2.5.5.jar, spring-osgi-core-1.1.2.jar, spring-osgi-extender-1.1.2.jar, spring-osgi-is-1.1.2.jar, spring-osgi-web-1.1.2.jar, spring-osgi-web-extender-1.1.2.jar, spring-web-2.5.5.jar
而後須要在 Web Application 的 struts.xml 中加入對象工廠的配置,配置以下:
清單 12. 配置對象工廠
<constant name="struts.objectFactory" value="osgi" /> <constant name="struts.objectFactory.delegate" value="springOsgi" />
在 Web Application 的 web.xml 加入 Spring 的監聽器 , 配置以下:
清單 13. 配置 Spring 監聽器
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <context-param> <param-name>contextClass</param-name> <param-value> org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext </param-value> </context-param> <context-param> <param-name>contextConfigLocation</param-name> <param-value>osgibundle:/META-INF/spring/*.xml,/spring/*.xml </param-value> </context-param> <context-param> <param-name>parentContextKey</param-name> <param-value>parent-context-bean</param-value> </context-param>
在 OSGi bundle 中,若是須要用 Spring 來管理對象,則把 Spring 對象的配置文件放在 /META-INF/spring/ 目錄下面,並以 xml 爲擴展名便可。Spring 對象配置文件的寫法在不少 Spring DM 的書籍或文章中都有講解,這裏再也不重複。最後須要在 MANIFEST.MF 中加入以下聲明用來配置 Spring 上下文和對象建立機制,create-asynchronously 的值爲 true 表示能夠容許異步建立對象。
清單 14. 配置 Spring 對象建立方式
Spring-Context: *;create-asynchronously:=true
本文首先對 OSGi 和 Felix 進行了簡要的介紹,而後經過一個示例詳細介紹了使用 Felix 和 Struts 開發 Web 應用,演示了 OSGi 的動態部署特性。隨後,講解了 OSGi bundles 管理經常使用的命令操做,以及在開發過程當中的幾個常見的問題的解決方法。