Java Portlet 規格自 JSR 168(2003 年發布)以來廣獲業界支持,但首版功能僅涵蓋基礎用例,導致各 Portal 廠商紛紛自行擴展,造成標準不一、互通性差。為解決此問題,JCP 於 2005 年 11 月啟動 JSR 286(Java Portlet Specification V2.0)開發,旨在提供標準化的高階功能解決方案。JSR 286 於 2008 年 3 月正式發布,完全相容 JSR 168,並基於 J2EE 1.4,可利用 JSP 2.0 等增強特性。
JSR 168 兩種舊模式缺點:
JSR 286 解決方案:引入可選介面 ResourceServingPortlet 及 serveResource(ResourceRequest, ResourceResponse) 方法。Portlet 透過 PortletResponse.createResourceURL() 產生資源 URL,攜帶當前 Portlet 瞬時狀態(模式、視窗狀態、渲染參數),但不允許修改狀態;可額外附加資源參數。容器僅輸出 serveResource() 回傳內容,無額外 Portal 修飾,效能大幅提升,Portal 僅扮演代理角色。
// 介面實作範例
public class TestPortlet implements Portlet, ResourceServingPortlet {
public void serveResource(ResourceRequest req, ResourceResponse resp)
throws PortletException, IOException {
// 業務邏輯與存取控制
getPortletContext().getRequestDispatcher("/WEB-INF/jsp/TestPortletResource.jsp")
.include(req, resp);
}
}
// JSP 中產生資源 URL
<a href="<portlet:resourceURL/>">請求資源</a>
採鬆耦合代理事件模型:Portlet 於部署描述符宣告可發布/處理的事件,執行期由 Portal 管理員連線。事件非可靠訊息服務,開發者需容忍遺失;事件鏈衍生深度可能受容器限制以防死鎖。
<event-definition>):指定名稱(支援預設命名空間或自訂 QName)與 <value-type>(簡單型別或實作 Serializable 的複雜物件)。<supported-publishing-event>):於 <portlet> 內宣告支援發布的事件。<supported-processing-event>):於 <portlet> 內宣告支援處理的事件。processAction() 呼叫 actionResponse.setEvent(eventName, eventObject)。EventPortlet 介面,於 processEvent(EventRequest, EventResponse) 中透過 eventRequest.getEvent() 取得事件物件,再以 getName()/getQName() 取名稱,getValue() 取值。JSR 286 的 GenericPortlet 新增實作 EventPortlet 與 ResourceServingPortlet,提供 processEvent()、serveResource() 預設實作,簡化開發。

JSR 168 僅有私有渲染參數(Portlet 內部使用)。JSR 286 新增共享渲染參數,多個 Portlet 宣告支援同一參數識別碼,任一 Portlet 以 setRenderParameter() 修改,其他 Portlet 於渲染階段自動可見,無需事件處理開銷。
<public-render-parameter><identifier>paramName</identifier></public-render-parameter>(<portlet-app> 直屬)。<supported-public-render-parameter>paramName</supported-public-render-parameter>(<portlet> 內)。天氣 Portlet 以郵遞區號為共享參數;地圖、旅遊資訊 Portlet 同樣宣告支援。使用者於天氣 Portlet 切換城市,其他 Portlet 即時同步更新。
// 設定共享參數
actionResponse.setRenderParameter("zipCode", "10001");
// 取得共享參數
String zip = renderRequest.getParameter("zipCode");
JSR 286 標準化過濾器,解決廠商擴展不相容問題。過濾器非 Portlet,不產生回應,但可攔截請求/回應進行預處理/後處理(修改標頭、資料、攔截調用)。
| 過濾器類型 | 攔截方法 |
|---|---|
| Action Filter | processAction(ActionRequest, ActionResponse) |
| Render Filter | render(RenderRequest, RenderResponse) |
| Resource Filter | serveResource(ResourceRequest, ResourceResponse) |
| Event Filter | processEvent(EventRequest, EventResponse) |
所有過濾器實作 PortletFilter(init()、destroy())及各自的 doFilter()。部署描述符以 <filter> 定義類別與 <lifecycle>,以 <filter-mapping> 繫結 Portlet(支援通配符)。單一過濾器可服務多 Portlet、跨多生命週期;單一 Portlet 可掛載多過濾器形成鏈。

JSR 168 僅間接透過 Session 屬性前綴反映窗口 ID。JSR 286 新增 PortletRequest.getWindowID(),由容器產生唯一 ID。同一 Portlet 於頁面多處佈局時,可用 Window ID 區分實例,進而針對不同窗口快取資料(如緩慢 Web Service 呼叫結果),避免重複請求。
本文系統梳理了 JSR 286 五大核心增強功能的理論基礎、宣告機制與運作原理。後續兩篇將透過 Apache Pluto 2.0 實際開發部署範例,示範各特性的具體應用步驟,敬請期待。