比較 JSR 168 Java Portlet 規範與 IBM Portlet API

文詳細說明了 Java TMJava TMStandardization Request for the Java Portlet 規範(以下稱爲 JSR 168)和 IBM WebSphere Portal Version 5.0 支持的 IBM Portlet API之間的相似點和不同點。文中解釋了 JSR 168 的一些基本概念和特徵,並將它們與 IBM Portlet API 進行了比較,並且爲兩個 Portlet 編程接口中的每一個都提供了一個示例 Portlet。本文是爲那些對 Java 編程、Servlet 或Portlet 編程有良好理解並具備 portal 方面的基本知識的 Portlet 開發人員和 Portal 架構師準備的。

引言


隨 着越來越多的企業Portal 的出現,不同的廠商已經創建了各種用於 Portal 組件的 API(稱爲 Portlet)。存在多種不兼容的接口產生了應用程序提供者、Portal 消費者和Portal 服務器廠商之間的問題。Java Community Process(JCP)定義的Java Portlet 規範(JSR 168)提供了 Portlet 和 Portal之間的互操作性的標準。它將及時解決這些問題。

本文檔的目的是比較 JSR 168 和 WebSpherePortal V5.0 Portlet API,並且給出了一些示例。

重要:爲了給顧客和合作伙伴提供一個方便的移植窗口,WebSphere Portal的下一個版本仍將支持 IBM Portlet API。





回頁首


JSR168 簡介


Java Standardization Request 168(JSR 168)定義了一個Portlet規範,包括 Portlet 容器和 portlet 之間的合約。JSR 168 是由Java Community Process(JCP)定義的。JSR 168 是由 IBM 和 Sun共同領導的,並且有一個很大的 Expect Group以幫助創建目前可用的最終版本。這個專家組由 Apache Software Foundation、Art Technology Group Inc.(ATG)、BEA、Boeing、Borland、Citrix Systems、Fujitsu、Hitachi、IBM、Novell、Oracle、SAP、SAS Institute、Sun、Sybase、Tibco和 Vignette 組成。關於這個 JSR 的更多信息可以在 http://jcp.org/en/jsr/detail?id=168上找到。

重要:在本分析中提供的信息不承擔任何性質的保證之責。本分析旨在爲讀者提供一個指示性的工具。

JSR 168定義


這部分提供了基本的 JSR 168定義,目的是幫助您理解 JSR 168 如何融入整個 Portal體系結構中。

Portal 和 Portlet 容器


portal是一個 Web應用程序,它通常提供不同來源的個性化、單點登錄的內容集合,並且託管不同後端系統的表示層。

Portal的主要任務是將不同的應用程序集合到一個頁面,這個頁面的外觀是Portal 用戶共有的。Portal也可以有複雜的個性化特徵,這些特徵能夠給用戶提供自定義內容。Portal頁可以有不同的 Portlet 集,以便爲不同的用戶創建內容。

圖1展 示了 Portal 的體系結構,比如一個由 WebSphere Portal 提供服務的Portal。客戶端請求由 PortalWeb 應用程序進行處理,它爲當前用戶檢索當前頁上的 Portlet。然後,Portal Web應用程序爲每個 Portlet 調用 Portlet 容器來通過 Container Invoker API 檢索它的內容。Portlet 容器通過 Portlet API 調用Portlet。Container Provider Service Provider Interface(SPI)允許 portlet 容器通過Portal 檢索信息。


圖 1. 基本的 Portal 體系結構
基本的 Portal 體系結構

Portlet 容器運行 Portlet,給它們提供所需的運行時環境,並且管理它們的生命週期。它爲Portlet 首選項項提供持久性存儲,這使得能夠爲不同的用戶生成自定義輸出。

Portal 頁和 Portlet


圖2展 示了基本的 Portal 頁組件。Portal 頁表示一個完整的標記文檔並且聚集若干 Portlet 窗口;也就是說,它將不同的應用程序用戶界面組合到一個統一的表示中去。Portal 頁使用戶能夠通過登錄對話框向 Portal 驗證自己的身份以便訪問個性化的 Portal 視圖。大部分 Portal 頁包括一些導航機制以允許用戶導航到其他的 Portal 頁。


圖 2:Portal 頁
Portal 頁

Portlet 窗口包括:

  • 標題欄,帶有 Portlet 的標題
  • 修飾,包括用於更改 Portlet 的窗口狀態的按鈕(比如最大化或最小化 Portlet)和用於更改 Portlet 的模式的按鈕(比如顯示幫助或編輯預定義的 Portlet 設置)
  • 由 Portlet 產生的內容(也稱爲一個標記段)。

圖 3在不同的瀏覽器上展示了一個 Portlet 窗口。如您所見,該 portlet 產生的標記段並不侷限於 HTML,而可以是任何標記。


圖 3:在 HTML 瀏覽器(頂部)和 WML 瀏覽器(底部)中顯示的 Portlet
在 HTML 瀏覽器(頂部)和 WML 瀏覽器(底部)中顯示的 Portlet

Portlet 生命週期


基本的 portlet 生命週期必須:

  1. 初始化,使用初始化類初始化 Portlet 並將其放入服務中。
  2. 處理請求,處理不同種類的動作並呈現內容。
  3. 完成,使用銷燬類除去 Portlet。

Portlet 基於用戶與 Portlet 或 Portal頁的交互來接收請求。請求處理分爲兩個階段:

  1. 動作處理

    如果用戶單擊 Portlet上的鏈接,就會觸發動作。動作處理必須在頁面上的任何Portlet 呈現開始之前結束。在動作階段,Portlet可以改變 Portlet 的狀態。

  2. 呈現內容

    在呈現階段,Portlet 會產生要發送回客戶端的標記。呈現不應該改變任何狀態。它允許一個頁面重新刷新而不需要改變Portlet  的狀態。一個頁面上的所有 Portlet的呈現都可以並行執行。

圖 4描述從客戶端到 Portlet的請求流,並且更詳細地展示了動作和呈現階段。在這個示例中,Portlet A接收了一個動作。在該動作執行之後,頁面(A、B、C)上的所有Portlet 的呈現方法都被調用。


圖 4. 從客戶端到 Portlet 的請求流
從客戶端到 Portlet 的請求流

Portlet 模式


Portlet 執行不同的任務並根據它們的當前函數來創建內容。Portlet模式指示 Portlet 何時將執行函數。Portlet模式指定 Portlet應該執行哪一類任務和應該生成什麼樣的內容。當調用 Portlet時,Portlet 容器提供當前對 Portlet 的請求的模式。在處理動作請求時,Portlet可以程序化地改變它們的 Portlet 模式。

JSR 168 定義了三類 Portlet 模式:

  1. 必須支持的模式(語義同上)

    Edit
    顯示一個或多個視圖以讓用戶自定義個性化的 Portletsettings.Help
    Help
    顯示幫助視圖。
    View
    顯示 Portlet 輸出。
  2. 可選擇的客戶模式

    About
    顯示 Portlet 的目的、來源、版本和其他信息。
    Config
    顯示一個或多個配置視圖以讓管理員配置對所有用戶有效的Portlet 設置。
    Edit_defaults
    設置可修改的首選項的缺省值,這些首選項通常可以在EDIT 屏幕中進行更改。
    Preview
    呈現輸出而不需要後端連接或特定於用戶的可用數據。
    Print
    顯示適於打印的視圖。
  3. 特定於 Portal 廠商的模式

    這些模式只適用於特定的廠商Portal。

窗口狀態


窗 口狀態是分配給 Portlet 生成的 Portal 頁面空間的指示器。Portlet容器提供當前的窗口狀態給 Portlet,而 Portlet 通過窗口狀態來決定它應該呈現多少信息。然而,在處理動作請求時,Portlet也可以程序化地改變它們的窗口狀態。

JSR 168 定義瞭如下窗口狀態:

Normal
Portlet 與其他 Portlet共享空間,在產生它的輸出時應該對考慮這種狀態。
Maximized
與處於正常的窗口狀態相比,窗口有更真實的狀態來提供它的輸出。
Minimized
Portlet 應該只呈現最小的輸出或沒有輸出。

除了這些窗口狀態之外,JSR 168 允許 Portal 定義自定義窗口狀態。

數據模式


JSR 168爲 Portlet定義了不同的機制以訪問瞬態數據和持久性數據。

Portlet 可以設置和獲取下列作用域內的瞬態數據:

請求 請求有附加的數據,比如請求參數和屬性,與Servlet類似。請求可以包含一些特性,以允許進行擴展;也可以包含從Portal 傳送到 Portlet 的客戶端頭字段(反之亦然)。
會話 Portlet可以將數據存儲在具有全局作用域的會話中,以讓 Web應用程序中的其他組件訪問這些數據;也可以將數據存儲在Portlet作用域中,這個作用域是 Portlet 私有的。
上下文 Portlet可以將數據存儲在 Web 應用程序上下文中,與 Servlet類似。

Portlet 可以訪問這些作用域內的持久性數據:

每個  Portlet Portlet可以將配置和個人數據存儲在 Portlet引用中,以允許 Portlet 創建個性化的輸出。Portlet可以定義允許用戶在編輯模式下更改那些數據(例如股票報價)以及哪些數據是隻能由管理員在配置模式下進行更改的配置設置(例如股票報價服務器)。
每個用戶 Portlet可以讀取用戶概要信息來針對用戶調整它的輸出(例如顯示用戶所在城市的天氣情況)。

Portlet 應用程序


所有資源、Portlet 和配置描述符一起打包成一個 Web應用程序檔案文件(WAR)。有兩個配置描述符:

  • 所有不是 Portlet 的 Web 應用程序資源必須在 web.xml配置描述符中指定。
  • 所有 Portlet 和 Portlet 相關的設置必須在 portlet.xmldeployment 描述符中指定。




回頁首


比較 JSR 168 與 IBM Portlet API


這一部分大概地比較了 JSR 168 Portlet API 與 IBM Portlet API。首先講解了一些相似的概念,接着說明了兩者之間的不同之處。

相似點


下列概念在 JSR 168和 IBM Portlet API 中是非常相似的。

特徵 相似點 不同點
Portlet 模式 兩者都支持基本的 Portlet 模式:Edit、Help 和 View。 這種配置模式在 JSR 168中是可選的。IBM Portlet API 不支持其他可選的 JSR 168模式(About、Edit_defaults、Preview、Print)。
窗口狀態 支持如下窗口狀態:Maximized、Normal和 Minimized。 Solo 窗口狀態只有 IBM Portlet API支持。
Portlet 生命週期 生命週期是相同的:初始化、處理請求、毀壞。 沒有
請求處理 請求處理分爲處理用戶動作的動作階段和產生標記的呈現階段。 沒有
URL 編碼 兩者都支持創建指向 Portlet或資源的 URL。 沒有
包含 Servlet/JSP Servlet 和 JSP 可以包括在 Portlet中。 沒有
Portlet 會話 Portlet可以存儲瞬態信息,這些信息應該都在會話中的請求內。 沒有
Portlet 應用程序打包 兩者都通過附加的部署描述符(名爲 portlet.xml )來將 Portlet 應用程序打包成 WAR文件。 portlet.xml 格式有所不同。
基於到期時間的緩存 Portlet可以支持基於到期時間的緩存。 API使用不同的機制來實現這一功能。
IBM Portlet API使用輪詢機制,在這種機制中, Portal 查詢 Portlet以獲取標記的有效時間,而在 JSR 168 中,Portlet可以將到期時間附加到創建的每個標記上。在用戶之間共享緩存條目只有在IBM Portlet API 中才是可能的。

不同點


JSR168 和 IBM Portlet API 在以下幾個方面有所不同。

特徵 IBM Portlet API JSR 168
Portlet 應用程序實體 使您可以通過部署描述符將抽象的Portlet 應用程序及其不同的實例定義爲具體的 Portlet應用程序。這允許重用抽象的 Portlet應用程序的設置,而只重寫對於每個具體的 Portlet應用程序惟一的部分。 該部署描述符緊跟在 web.xml 部署描述符之後,它定義一個應用程序及其 Portlet定義。
Portlet 實體 Web 部署描述符中的每個 Portlet配置都有一個 Portlet 對象實例。根據 Flyweight模式,可以有許多 PortletSettings 對象來參數化相同的 Portlet對象(基於每個請求)。 PortletSettings 中的改變會應用到這個具體的 Portlet 中的所有 Portlet實例。用戶也可以具體 Portlet的個人視圖,它是通過使用自定義輸出的 PortletData 呈現的。 PortletSettingsPortletData 合併成一個名爲 PortletPreferences 的對象。
請求/響應對象 Portlet在呈現調用中接收到的請求/響應對象與動作調用接收到的相同。 在 JSR 168中,有兩個不同的對象。

JSR 168 獨有的特徵


這些項只適用於 JSR 168。

特徵 描述
呈現參數 呈現參數允許 Portlet存儲它的導航狀態。
呈現參數在隨後的呈現請求中保持不變,而只是在Portlet接收到一個新的動作時才發生改變。這啓用了書籤功能,並且解決了瀏覽器後退按鈕問題。
全局 HttpSession 作用域 Portlets 不僅可以存儲具有 Portlet可見性的數據,而且可以存儲具有整個 Web 應用程序可見性的數據。
重定向 Portlet可以重定向到動作階段中的其他 Web 資源。

IBM Portlet API 獨有的特徵


以下概念只適用於 IBM?Portlet API。

特徵 描述
事件 事件可以在?Portlet之間發送。
附加的生命週期偵聽器 除了動作和呈現之外,生命週期偵聽器(比如首頁)也不適用於 JSR168 的第一個版本。
Portlet 菜單 讓 Portlet可以提供內容到菜單欄,以便通過 Portal頁使導航更加容易。
基於無效時間的緩存 讓 Portlet可以顯式使緩存的內容無效。




回頁首


示例 Portlet


這一部分展示一個 HelloWorld Portlet,在兩個 API的每一箇中都有它的實現。其主要的不同之處突出顯示在第二個Portlet 中。

這兩個示例 Portlet 實現了相同的功能:

  • 將 JSP 用於呈現輸出
  • 根據特定於用戶的數據自定義 Portlet 輸出
  • 處理動作以允許用戶改變這些動作

這個示例展示了一個個性化的 HelloWorld Portlet。它通過名稱識別用戶,並且允許用戶進入編輯模式以設置一個新的名稱。在 action方法(在 IBM Portlet API 中稱爲 actionPerformed ,而在 JSR 168中稱爲 processAction )中,Portlet持久化存儲用戶輸入的新用戶名。

清單 1. IBM Portlet API 示例 Portlet

首先看一看通過 IBM Portlet API 實現的 Portlet。

public class HelloWorld extends AbstractPortlet implements ActionListener
{
public void doView (PortletRequest request, PortletResponse response)
throws PortletException, IOException
{
//Get the user's name to display from persistent storage
PortletData portletData = request.getData();
String stringToDisplay=(String)portletData.getAttribute("userName");
if (stringToDisplay == null) {
stringToDisplay = defaultString; // set default string
}
// Add the display string to the portlet request to make it
// accessible by the view JSP
request.setAttribute("userName", stringToDisplay);
// Get a context for the current session for invoking the JSP
PortletContext context = getPortletConfig().getContext();
context.include(viewJSP, request, response);
}
public void doEdit(PortletRequest portletRequest,
PortletResponse portletResponse )
throws PortletException, IOException
{
// Create the cancel return URI for the edit page
PortletURI cancelURI = portletResponse.createReturnURI();
// Preserve the Cancel URI in the request to make it
// accessible by the edit JSP
portletRequest.setAttribute("cancelURI", cancelURI.toString());
// Create the save URI for the edit page
PortletURI saveURI = portletResponse.createReturnURI();
// For the "Save" button the return URI must include the "Save" action
// so the Action Listener for this portlet will be invoked
SimpleActionHelper.
addSimplePortletAction(getPortletConfig().getContext(), saveURI, "save");
// Preserve the Save URI in the request to make it accessible by
// the edit JSP
portletRequest.setAttribute("saveURI", saveURI.toString());
//Get the user's name to display from persistent storage
String stringToDisplay = (String)
portletRequest.getData().getAttribute("userName");
if (stringToDisplay == null) {
stringToDisplay = defaultString; // none found, set default string
}
// Add the display string to the request to make it accessible by the
// edit JSP as an inital value of the input field on the edit form
portletRequest.setAttribute("userName", stringToDisplay);
// Get a context for the current session for invoking the JSP
PortletContext context = getPortletConfig().getContext();
context.include(editJSP, portletRequest, portletResponse);
}

public void actionPerformed(ActionEvent event) {
String action =
SimpleActionHelper.getActionString(getPortletConfig().getContext(),
event);
HelloWorld helloPortlet = (HelloWorld)event.getPortlet();
PortletLog log = helloPortlet.getPortletLog();
// If this is a save action, then see if the user specified a name
if ( action!=null ) {
if ( action.equals("save") ) {
PortletRequest request = event.getRequest();
PortletData portData = request.getData();
String userName = request.getParameter("userName");
try {
// Save the name specified by the user
if ( userName != null ) {
portData.setAttribute("userName", userName);
portData.store();
}
} catch ( AccessDeniedException ade ) {
} catch ( IOException ioe ) {
log.error( "

Couldn't write the user date to
persistence because an I/O Error occurred.

" );
}
}
}
}
}


清單 2. JSR 示例Portlet

現在,看一看已修改爲使用 JSR 168 的同一 Portlet。更改過的文本用粗體標記。

public class HelloWorld extends 
GenericPortlet
{
public void doView (
RenderRequest request,
RenderResponse response)
throws PortletException, IOException
{
//Get the user's name to display from persistent storage
Portlet
Preferences portletData = request.get
Preferences(); String stringToDisplay = (String) portletData.get
Value("userName",
defaultString);
// Add the display string to the portlet request to make it
// accessible by the view JSP
request.setAttribute("userName", stringToDisplay);
// Get a context for the current session for invoking the JSP

PortletRequestDispatcher rd = getPortletContext().getRequestDispatcher(viewJSP);
rd
.include(request, response);
}
public void doEdit(
RenderRequest portletRequest,

RenderResponse portletResponse )
throws PortletException, IOException
{
// Create the cancel URI for the edit page
PortletUR
L cancelURI = portletResponse.create
ActionUR
L();
// Preserve the Cancel URI in the request to make it
// accessible by the edit JSP
portletRequest.setAttribute("cancelURI", cancelURI.toString());
// Create the save URI for the edit page
PortletUR
L saveURI = portletResponse.create
ActionUR
L();
// For the "Save" button the return URI must include the "Save" action
// so the Action Listener for this portlet will be invoked

saveURI.setParameter("action", "save");
// Preserve the Save URI in the request to make it accessible by
// the edit JSP
portletRequest.setAttribute("saveURI", saveURI.toString());
//Get the user's name to display from persistent storage
String stringToDisplay = portletRequest.get
Preferences().get
Value("userName",

defaultString);
// Add the display string to the request to make it accessible by the edit JSP
// as an inital value of the input field on the edit form
portletRequest.setAttribute("userName", stringToDisplay);
// Get a context for the current session for invoking the JSP

PortletRequestDispatcher rd = getPortletContext().getRequestDispatcher(editJSP);
rd.
include(portletRequest, portletResponse);
}
public void
processAction(ActionRequest request, ActionResponse response)
throws PortletException, IOException

{
String action =
request.getParameter("action");
// If this is a save action, then see if the user specified a name
if ( action!=null ) {
if ( action.equals("save") ) {
Portlet
Preferences portData = request.get
Preferences();
String userName = request.getParameter("userName");
// Save the name specified by the user
if ( userName != null ) {
portData.setAttribute("userName", userName);
portData.store();
}
}


IBM Portlet API 版本的 Portlet 的主要區別在於:

  1. 請求分配器使用

    JSR 168 和 IBM Portlet API之間的請求分配器使用略有不同。JSR 168使用相同的機制以從上下文中取得一個請求分配器作爲 servlet API 的參數。JSR168使用相同的機制從帶有路徑的上下文中獲取請求分配器,以作爲與Servlet API 一樣的參數。

  2. 持久性 Portlet 數據

    JSR 168 的持久性數據 PortletPreferences 有一個類似於 JDK 1.4 Preferences 的API,並且允許爲 get方法指定缺省值。不再需要檢查零返回值並且顯式設置缺省值的代碼。JSR168 只允許 String 和 String 數組作爲首選項的值;IBMPortlet API 可以支持任意對象。

  3. 動作處理

    在 JSR 168 中的動作處理被簡化,並且不需要創建特定的動作對象被創建。自動創建動作 URL會觸發調用 processAction 方法。





回頁首


結束語


由於 JSR 168 的定義,最終有了一個標準的 Portlet API,它提供獨立於 Portal 廠商的編程 Portlet,並且使您可以在不同的 Portal 上不加改變地運行相同的 Portlet。

JSR 168 的概念與 IBM Portlet API 有密切的聯繫。與 IBM Portlet API 相比,JSR 168的功能是有限的,因爲它是 Portlet 規範的 第一個版本。熟悉 IBM Portlet API 的開發人員應該能夠毫無困難地儘快學會使用JSR 168 API。



參考資料

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章