OSGi(Open Service Gateway Initiative)是針對 Java 動態模組開發的規範,提供面向服務、基於元件的開發環境,標準化管理軟體全生命週期。OSGi 適合需長時間運行、動態更新且對環境影響最小的系統,特點包括低複雜度、高可重用、動態部署、持續運行、開發簡單。
Felix 是 Apache 旗下的 OSGi 開源實作,目標是提供完全相容的 OSGi 框架與標準服務實作,目前功能穩定。雖然 Spring DM + Jetty 等方案已有介紹,但與傳統 Web 開發差異大,轉換困難且穩定性待驗證。幸運的是,Struts 2.1.8.1 加入 Felix OSGi 支援,讓傳統 Web 開發能整合 OSGi 模組管理平台,開發模式幾乎無變化,應用仍可部署於 Tomcat、JBoss 等容器。

範例包含五個工程:一個 Web Application 工程、四個 OSGi Bundle 工程:
兩個實作 Bundle 可動態部署替換,展現 OSGi 模組化與動態部署能力。
web.xml 需配置 Struts 2 過濾器與 OSGi 監聽器:
<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>
StrutsOsgiListener 負責啟動/停止 Apache Felix Host(Struts OSGi Plugin 與 Felix Framework 連接點),Felix Host 創建初始化 Felix Framework 管理所有 Bundle。Struts OSGi Plugin 監聽 Bundle 變化,若為 Struts Bundle 則載入其配置。

部署至 Tomcat 啟動後,瀏覽 https://localhost:8080/webapp/osgi/admin/bundles.action 若看到 Bundle 列表(
)即配置成功。
使用 Eclipse 新增 Plug-in 專案,設定 OSGi framework 為 standard。定義介面:
package com.example.time.service;
public interface TimeService {
public String getTime();
}
在 MANIFEST.MF 的 Export-Package 暴露此套件供其他 Bundle 使用。
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);
}
}
在 Activator.start() 發布服務:
public void start(BundleContext context) throws Exception {
context.registerService(TimeService.class.getName(), new LocalTimeService(), null);
}
Import-Package 引入介面服務套件。
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:ss");
return "The UTC time:" + formatter.format(date);
}
}
同樣在 Activator 發布服務,用於動態替換本地時間服務。
MANIFEST.MF 關鍵配置:
Manifest-Version: 1.0
Bundle-SymbolicName: com.example.time.web
Struts2-Enabled: true
Import-Package: com.example.time.service,
com.opensymphony.xwork2,
org.apache.struts2.osgi.interceptor,
org.osgi.framework;version="1.3.0"
Struts2-Enabled: true 標識此為 Struts Bundle,其 struts.xml 會被獨立載入。
Action 實作:
public String execute() {
ServiceReference ref = bundleContext.getServiceReference(TimeService.class.getName());
TimeService timeService = (TimeService) bundleContext.getService(ref);
timeMessage = timeService.getTime();
return SUCCESS;
}
struts.xml(繼承 osgi-default 套件):
<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 目錄(
)。
https://localhost:8080/webapp/time/time.do → 顯示本地時間(
)https://localhost:8080/webapp/osgi/admin/shell.do),執行 stop 1 停止 Local Time Service Bundle,ps 確認狀態變為 Resolved(
)/time/time.do → 顯示 UTC 時間(
)此過程展示 OSGi Bundle 動態部署能力:無需重啟應用即可熱替換服務實作。
| 操作 | 命令 | 範例 |
|---|---|---|
| 安裝 | install <bundle-url> 後 start <bundle-id> | install file:/k:/plugins/...jar, start 7 |
| 更新 | update <bundle-id> <bundle-url> | update 1 file:/k:/plugins/...jar |
| 啟動/停止 | start/stop <bundle-id> | start 2, stop 1 |
| 卸載 | Installed/Resolved 狀態:uninstall <id>;Active 狀態:先 stop 再 uninstall | uninstall 1 |
預設最大層級為 3。在 web.xml 新增:
<context-param>
<param-name>struts.osgi.runLevel</param-name>
<param-value>5</param-value>
</context-param>
若無法連接 https://struts.apache.org/,修改 DTD URL 為本地或可連線位址:
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"file:/c:/webapp/dtds/struts-2.0.dtd">
加入 Spring DM 相關 JAR(spring-osgi-core, spring-osgi-extender 等),在 struts.xml 配置物件工廠:
<constant name="struts.objectFactory" value="osgi" />
<constant name="struts.objectFactory.delegate" value="springOsgi" />
web.xml 加入 Spring 監聽器與上下文配置,Bundle 中 Spring 配置放於 /META-INF/spring/*.xml,MANIFEST.MF 加入:
Spring-Context: *;create-asynchronously:=true
本文透過完整範例展示 Felix + Struts 2 整合開發 OSGi Web 應用,演示動態部署特性,並說明 Bundle 管理命令與常見問題解決方案,為傳統 Web 開發引入 OSGi 模組化能力提供實用參考。