使用 Felix 和 Struts2 開發 Web 應用

引言

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

回頁首瀏覽器

使用 Felix 和 Struts 開發 Web 應用示例

下面講解的示例是一個獲取時間信息並在 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. 示例結構原理圖

圖 1. 示例結構原理圖

創建 OSGi 的 Web 開發環境

本文示例使用的 Web 開發環境包括以下組件,部分框架能夠 參考資料中下載。

  • Eclipse 3.5 for Java EE Developers
  • Sun JDK 1.6.0
  • Tomcat 6.0.24
  • Struts 2.1.8.1 (Essential Dependencies Only)
  • spring-osgi-1.1.2-with-dependencies (Only Need when adding Spring Support)

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 工程的目錄結構

圖 2. Web Application 工程的目錄結構

將 Web Application 部署到 Tomcat 上並啓動 Tomcat,而後在瀏覽器中輸入 http://localhost:8080/webapp/osgi/admin/bundles.action, (webapp 是項目部署到 Tomcat 中的名字 ) 若是看到了相似於 圖 5的 bundles 列表,說明 OSGi 環境配置成功。

開發獲取時間消息接口服務 bundle

消息接口服務 bundle 是提供消息服務的接口,該接口將被 Web bundle 所使用,其餘 bundle 能夠不一樣的形式實現該接口。在這裏利用 eclipse 新建插件工程的功能來建立 OSGi bundle。須要特別設置 an OSGI framework 爲 standard 方式,這種方式容許部署項目到標準的 OSGI 容器中。新建 OSGi 工程的嚮導如圖 3 所示。

圖 3. 新建 OSGi 工程嚮導圖

圖 3. 新建 OSGi 工程嚮導圖

在該項目中開發一個用於獲取時間信息的接口,經過該接口能夠獲取字符串形式的時間信息。

清單 2. 獲取時間服務接口代碼

package com.example.time.service; 

 public interface TimeService{ 
 public String getTime(); 
 }

須要將該 bundle中的服務包的類和接口就暴露給了其餘的 bundle,其餘的 bundle能夠經過 import這個包來使用其中的類和接口。

開發獲取本地時間消息實現服務 bundle

獲取本地時間消息服務 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

獲取 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 是系統 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 目錄結構

圖 4. Web Application 中的 bundles 目錄結構

運行演示

啓動 Tomcat,在瀏覽器地址欄輸入 http://localhost:8080/webapp/osgi/admin/bundles.do, 能夠看到所繫統中全部的 bundle 的列表。

圖 5. 部署的 bundles 列表

圖 5. 部署的 bundles 列表

在瀏覽器地址欄輸入 http://localhost:8080/webapp/time/time.do,能夠得到時間信息,此時的時間信息爲本地時間信息,當前 TimeService 這個服務有 local time service 和 UTC time service 兩個實現,調用的是 local time service 這個實現。

圖 6. 獲取本地時間頁面顯示

圖 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 管理頁面

圖 7. OSGi Shell 管理頁面

在瀏覽器地址欄再次輸入 http://localhost:8080/webapp/time/time.do 獲得的結果如圖 7 所示。

圖 8. 獲取 UTC 時間顯示頁面

圖 8. 獲取 UTC 時間顯示頁面

經過上面的演示,咱們能夠看到 OSGi bundle 的動態部署能力。

回頁首

bundle 的管理

經過 Felix 能夠方便的管理項目中的 bundle,而且實現 bundle 的熱部署,即插即用,即刪即無的特性,特別適用於可持續運行的系統。

添加 bundle

輸入命令 install <bundle-url>,而後輸入 start <bundle-id> 便可。如 $install file:/k:/plugins/com.example.time.local_1.0.0.qualifier.jar , $start 7

更新 bundle

輸入命令 update <bundle-id> <bundle-url> 便可。如

$ update 1 file:/k:/plugins/com.example.time.local_1.0.0.qualifier.jar

啓動和中止 bundle

輸入命令 start <bundle-id> 啓動 bundle;輸入命令 stop <bundle-id> 中止 bundle。如

$ start 2 , $ stop 1

卸載 bundle

若 bundle 處於 Installed 或 Resolve 狀態,則直接輸入命令 uninstall <bundle-id>。若 bundle 處於 Actived 狀態,則先輸入命令 stop <bundle-id> 中止 bundle, 再輸入命令 uninstall <bundle-id>。如 $ uninstall 1

回頁首

常見問題

如何修改 bundle 的最大啓動級別

在上面的示例中,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>

解決 Bundle 中的 struts.xml 的 Struts Configuration DTD 沒法定位的問題

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 進行對象管理

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 管理經常使用的命令操做,以及在開發過程當中的幾個常見的問題的解決方法。

相關文章
相關標籤/搜索