Portlet 2.0 新特性介紹(全)

第一部分 Portlet 2.0 新特性介紹

======================================================================

  關於本系列

  本系列文章專門針對具有 JSR 168 Portlet 開發基礎,並且想了解 JSR 286 Portlet 新特性和開發流程的開發人員。在學習完本系列後,您將瞭解相對於JSR 168 Portlet,JSR 286 Portlet 究竟提供了哪些增強功能, 以及這些新增特性在實際開發中的應用。

  第 1 部分將簡單回顧 JSR168 Portlet, 並列出了 JSR 286 Portlet 的新增內容。

  第 2 部分和第 3 部分將通過在 Apache Pluto 2.0 平臺上開發和部署 Portlet 應用程序, 向讀者介紹 JSR 286 Portlet 新特性的使用方法。

  關於本文

  本文假定讀者熟知 JSR 168 Portlet,並對 J2EE 基本常識有一定了解。本文主要以理論的方式向讀者介紹 JSR 286 Portlet 的以下新增特性:

  資源服務

  事件

  共享呈現參數

  Portlet 過濾器

  Portlet 窗口

  Portlet 和 JSR 168

  Portlet 是部署在容器內用來生成動態內容的 Web 組件,與 servlet 類似,portlet 的整個生命週期從 init 到 destroy 的過程都在 portlet 容器中進行。Java Portlet Specification 對 portlet API、標準化用戶數據、參數設置、portlet 請求以及響應、部署、打包以及安全等方面都做了詳細的規定,以此來實現portlet 之間以及 portlet 與 portlet 容器之間的交互和協作。 Java PortletSpecification 1.0, 即 Java Specification Request(JSR)168 發佈於 2003 年 10 月。

  JSR 286 及其新特性

  JSR 168 目前在業界受到廣泛支持,而且它由開放源碼支持。標準和產品的第一個版本存在一定的缺陷,僅支持最基本的用例,在功能上有一些限制。而且 Java Portlet Specification V1.0 也存在這種情況,因此,經過三年之後,大多數支持 Java Portlet Specification V1.0 的門戶產品都提供一些附加擴展,以支持更高級的用例,這些附加的擴展造成了各個門戶產品的標準不統一,彼此間的交互協作成了不可避免的問題。爲了更好地規範 portlet 開發,以適應業界發展,並提供適應於最高級別用例的標準解決方案,從而爲這些高級功能提供互操作性,在 2005 年 11 月開始了 Java Portlet Specification V2.0(稱爲 JSR 286)的開發,Java Portlet Specification V2.0目前已經進入 Finaldraft 的等待審批階段,並計劃在2008 年 3 月正式發佈。JSR 286 最終草案兼容了 JSR 168,並完善了 JSR 168 的部分功能,並提供了諸多 JSR 168 所沒有的新特性,例如資源服務、事件、portlet 過濾器、共享呈現參數及 portlet 窗口等。與 V1.0 類似,V2.0 也將基於 J2EE 1.4,因此可讓 Portlet 使用 J2EE 1.4 增強(如 JSP 2.0)。下面是該新規範的一些主要功能及特性:

  資源服務:一種新的通過 portlet 呈現資源的方式。

  事件:通過發送事件和接收事件來實現 portlet 之間的通信

  Portlet 過濾器:與 servlet 過濾器類似,根據 Portlet 請求和響應動態的呈現內容的變換。存在以下四種類型的 portlet 過濾器:

  Action 過濾器

  Render 過濾器

  Resource 過濾器

  Event 過濾器

  共享呈現參數:除了 portlet 私有的呈現參數之外,新增了可以在 portlet 之間共享的呈現參數。

  Portlet 窗口:提供 portlet 窗口 ID 供 portlet 使用。

  下面我們將對 JSR 286 所提供的這些新功能及其使用逐一做詳細介紹。

  資源服務

  在 JSR 168 中,Portlet 服務於資源的方法只有兩種:直接鏈接到資源,或者通過 Portlet 服務於資源。兩種方法分別適用於不同目的的需要,各有優缺點。

  直接鏈接對於所有 Portlet 狀態都相同的靜態資源非常有效,但對於其他用例效果卻不太好,因爲需要考慮來自 Portlet 上下文的信息。這樣的示例包括基於 Portlet 模式、窗口狀態、當前呈現參數或 Portlet 首選項呈現不同資源。

  以一個 JSP 文件 test.jsp 爲例,如果要訪問該資源,可以直接通過超鏈接訪問該文件,如清單 1 所示:

清單 1. 直接訪問資源文件

<a href="<c:url value="/test.jsp" />">test.jsp</a> 

  或者通過 Servlet 轉向,如清單 2 和清單 3 所示:

清單 2. 直接訪問 Servlet

<a href="<c:url value="/testServlet" />">testServlet</a> 

清單 3. Servlet 對資源文件的訪問控制

public void service(ServletRequest request, ServletResponse response) 
  throws ServletException,IOException { 
  ... 
  在此添加訪問控制等業務邏輯代碼 
  ... 
  RequestDispatcher rd = request.getRequestDispatcher("/WEB-INF/jsp/test.jsp"); 
  rd.forward(request, response); //或者爲 rd.include(request, response); 
} 

  從清單 1、2、3 可以看到,直接鏈接到資源這種方式,無法訪問到相關 Portlet 的信息,包括 Portlet 模式、窗口狀態、當前呈現參數或 Portlet 首選項等。而通過 Portlet 呈現資源的優勢是可以通過門戶訪問資源,因此可以通過控制門戶訪問而對資源提供保護。但是,這也帶來了額外的門戶請求開銷,加重了門戶服務器的負載。

  爲了更好的解決這兩種方法的侷限性,JSR 286 採用了一種新的資源服務方式 —— Portlet 資源服務。即 JSR 286 引入了一個新的具有 serveResource 方法的可選生命週期接口 ResourceServingPortlet ,該接口可以由 ResourceURL 觸發,Portlet 可以通過 PortletResponse.createResourceURL 方法創建它。資源 URL 包含當前 Portlet 的瞬時狀態(Portlet 模式、窗口狀態和呈現參數),但不能爲此狀態設置新值。資源 URL 可以有在資源 URL 上設置的其他資源參數。

  通過調用 ResourceServingPortlet 接口的 serveResource() 方法, Portlet 不僅可以通過控制門戶訪問而對資源進行保護,並且 Portlet 容器不會呈現任何除 serveResource() 方法返回的內容之外的附加輸出。這樣,用戶由於可以直接通過操作響應對象而被賦予了更多的控制權限,並且沒有額外門戶請求的開銷,減輕了門戶服務的負載。而 Portal 服務器此時只是充當了一個代理服務器的作用。

  JSR 286 資源服務的使用方法:

  Portlet 類需實現 javax.portlet.Portlet 和 javax.portlet.ResourceServingPortlet 接口並實現 serveResource() 方法, 如 清單 4 所示:

清單 4. Portlet 對資源文件的訪問控制

public class TestPortlet implements Portlet, ResourceServingPortlet 
{ 
  ...... 
  public void serveResource(ResourceRequest resourceRequest, 
    ResourceResponse resourceResponse) throws PortletException, 
    IOException { 
    ... 
    在此添加訪問控制等業務邏輯代碼 
    ... 
    PortletRequestDispatcher portletRequestDispatcher = portletConfig 
      .getPortletContext().getRequestDispatcher( 
      "/WEB-INF/jsp/TestPortletResource.jsp"); 
    portletRequestDispatcher.include(resourceRequest, resourceResponse); 
  } 
  ...... 
} 

  使用 JSP 標籤通過PortletResponse.createResourceURL 方法創建 RecourceURL:

清單 5. 創建資源訪問 URL

<a href="<portlet:resourceURL/>">Click me to request Resource URL</a> 

  所保護的訪問資源,在此例中即爲 TestPortletResource.jsp 。

  接下來,我們就可以充分體驗 JSR 286 資源服務新特性所帶來的簡單便捷以及高性能了。

  對照該介紹,讀者可參照本系列第 2 部分對資源服務特性的實例開發加深對該部分相關內容的理解。

  事件

  JSR 286 定義的事件模型是一種鬆耦合的代理事件模型。在此模型中,Portlet 定義可以接收以及在 Portlet 部署描述符中公佈的事件。在運行時,門戶管理員(或業務用戶)可以將不同的 Portlet 連接在一起。

  Portlet 事件服務並不是一個可信任消息服務(例如 JMS)的替代。很多情況下Portlet 事件並不能總是保證能夠傳送到目的地。因此 Portlet 必須能夠在部分或即使所有事件都不能正確接收的情況下仍然能夠工作。

  另外,有的時候 Portlet 爲了響應某一個事件,也會向另外的 Portlet 發佈新的事件,這樣就形成了事件的衍生代。這在一定程度上可能造成事件的死鎖,JSR 286 本身沒有對衍生代做出限制,但是很多 Portlet 容器會定義事件的最大衍生代以防止死鎖的發生。讀者在開發相關應用時請注意其本身的限制。

  事件聲明

  對於一個事件的聲明包括三個部分,分別是事件的定義聲明、事件的發佈載體聲明也就是發佈該事件的 Portlet 聲明、事件接收載體的 Portlet 聲明。

  事件定義聲明:我們需要在 portlet.xml 中使用 <event-definition> 元素對事件進行聲明,並且該元素與 <portlet> 元素並列作爲 <portlent-app> 的子元素。

清單 6.event-definition 聲明

<portlet-app id="myPortletApp" version="2.0"> 
 <portlet> 
 ... 
 </portlet> 
 <event-definition> . . .</event-definition> 
 ... 
</portlet-app> 

  對於一個事件的聲明有兩點需要注意:事件的名稱和值的類型。對於事件名稱,JSR 286 既可以爲事件定義默認的命名空間,其作用域爲所有未聲明 QName 的事件;也可以爲事件單獨定義自己的 QName。對於 QName 和命名空間的理解,請讀者參考 XML 規範的相關文檔,本文不做詳細介紹。對於事件值的類型,既可以是簡單的Java 對象,例如 Integer,String 等,也可以是預先定義的 Java 複雜對象,但是前提是該對象必須實現 Serializable 接口。其中 <event-definition> 的具體格式如 清單 7 和 清單 8 所示:


清單 7. 默認命名空間下事件定義聲明

<default-namespace>http://cn.ibm.com/</default-namespace> 
...... 
<event-definition> 
  <name>event-with-simple-value</name> 
  <value-type>java.lang.String</value-type> 
</event-definition> 
 
<event-definition> 
  <name>event-with-complex-value</name> 
  <value-type>com.ibm.jsr286.TestEventBean</value-type> 
</event-definition> 


清單 8. 自定義 QName 下事件定義聲明

<event-definition>  
  <qname xmlns:key="http://www.ibm.com">event-with-simple-value</qname> 
  <value-type>java.lang.Integer</value-type> 
</event-definition> 
 
<event-definition>  
  <qname xmlns:key="http://www.ibm.com">event-with-qname</qname> 
  <value-type>com.ibm.jsr286..TestEventBean</value-type> 
</event-definition> 

  事件發佈載體聲明:事件的發佈載體聲明需要在 portlet.xml 的 <portlet> 元素中用 <supported-publishing-event> 關鍵字。對應事件聲明格式,事件發佈載體 Portlet 聲明亦有默認命名空間和自定義命名空間以及簡單對象和複雜對象的情況,見示例:

清單 9. 默認命名空間下事件發佈聲明

<supported-publishing-event> 
  <name>event-with-simple-value</name> 
</supported-publishing-event> 
 
<supported-publishing-event> 
  <name>event-with-complex-value</name> 
</supported-publishing-event> 


清單 10. 自定義 QName 下事件發佈聲明

<supported-publishing-event> 
  <qname xmlns:key="http://www.ibm.com">event-with-simple-value</qname> 
</supported-publishing-event>  
 
<supported-publishing-event> 
  <qname xmlns:key="http://www.ibm.com">event-with-complex-value</qname> 
</supported-publishing-event>  

  事件接收載體聲明與事件發佈載體聲明類似,事件的接收載體聲明需要在 portlet.xml 的 <portlet> 元素中用 <supported-processing-event> 關鍵字。見示例清單 11 和 清單 12:

清單 11. 默認命名空間下事件接收載體聲明

<supported-processing-event> 
  <name>event-with-simple-value</name> 
</supported-processing-event> 
 
<supported-processing-event> 
  <name>event-with-complex-value</name> 
</supported-processing-event> 


清單 12: 自定義 QName 下事件接收載體聲明

<supported-processing-event> 
  <qname xmlns:key="http://www.ibm.com">event-with-simple-value</qname> 
</supported-processing-event> 
 
<supported-processing-event> 
  <qname xmlns:key="http://www.ibm.com">event-with-complex-value</qname> 
</supported-processing-event> 

  事件發佈接收與處理

  事件發佈:我們可以在希望發佈事件的 Portlet 的 processAction() 方法裏,通過調用 ActionResponse 的 setEvent() 進行事件發佈,setEvent() 方法的輸入參數爲事件的名稱和對應的值,這些參數必須與我們前面在 portlet.xml 中的事件聲明一致。

清單 13. 事件發佈

public class TestSenderPortlet implements Portlet 
{ 
  ...... 
  public void processAction(ActionRequest request, 
    ActionResponse response) throws PortletException, 
    IOException { 
    ...... 
    response.setEvent(eventName, eventObject); 
    ...... 
  } 
  ...... 
} 

  事件接收:事件的接收 Portlet 必須實現 javax.Portlet.EventPortlet 接口,事件的接收處理則在該接口包含 processEvent() 方法中進行,JSR 286 定義該方法提供了兩個輸入參數: EventRequest 和 EventResponse,我們可以通過調用 EventRequest 實例的 getEvent() 方法來獲得當前事件,該方法返回一個事件對象的實例,該實例封裝了事件的唯一標識和對應的值。

清單 14: 事件接收

public class TestReceiverPortlet implements Portlet,EventPortlet 
{ 
  ...... 
  public void processEvent(EventRequest request, 
    EventResponse response) throws PortletException, 
    IOException { 
    ...... 
    Event event = request.getEvent(); 
    ...... 
  } 
  ...... 
} 

  事件處理:獲得事件對象後,我們也可以通過 getQNames() 方法或者 getName() 獲得事件的名稱。兩種獲得事件方法的區別是 getQNames() 可以得到事件的全稱標識,而 getName() 只是取得本地標識名。而取得事件的值則可以通過事件的 getValue() 方法獲得。

清單 15: 事件處理

...... 
public void processEvent(EventRequest request, 
  EventResponse response) throws PortletException, 
  IOException { 
  ...... 
  Event event = request.getEvent(); 
  String name = event.getName();// 或者 String qname = event.getQNames(); 
  String value = event.getValue(); 
  ...... 
} 
...... 

  GenericPortlet API 的變化

  在 JSR 168 Portlet 的開發中,開發者通常繼承抽象類 javax.portlet.GenericPortlet 來實現自己的 Portlet 邏輯代碼。 同 JSR 168 相比, JSR 286 的 GenericPortlet 增加了 javax.portlet.EventPortlet 和 javax.portlet.ResourceServingPortlet接口的實現,從而增加了事件處理和資源服務的功能。讀者可以從 類圖 1 和類圖 2 看出 JSR 286 GenericPortlet API 的變化。

圖 1. JSR 168 GenericPortlet 類圖


圖 2. JSR 286GenericPortlet 類圖

  查看原圖(大圖)

  共享呈現參數

  我們先來看一看共享呈現參數的官方介紹:共享呈現用於在 Portlet 之間共享呈現參數,從而創建一個頁面上下文。共享呈現參數在 portlet.xml 文件中聲明。如果這些參數不是空值,Portlet 將自動接收它們。與非共享呈現參數相比,如果應該更改值,Portlet 僅需要設置共享呈現參數。

  共享呈現參數,顧名思義,就是指 Portlet 之間共享參數,每一個 Portlet 對該參數的修改都能夠直接被另外支持該參數 Portlet 所獲得。共享呈現參數與 JSR 168 中已經有私有呈現參數的區別就在於,私有呈現參數只爲 Portlet 內部使用,而共享呈現參數則爲多個 Portlet之間通信協作而設置。共享呈現參數與事件相比的優勢就在於避免了事件處理過程調用的繁瑣。

  我們舉一個簡單的事例來說明共享呈現參數的優點。假如我們開發了一個關於天氣的 Portlet, 這個 Portlet 可以根據選擇的城市來顯示該城市的天氣情況。我們爲這個 Portlet 定製了郵政編碼這個共有呈現參數來表示用戶選擇的城市。這樣,如果我們再開發一個也有這個共享呈現參數的 Portlet,例如顯示該城市地圖或者旅遊信息的 Portlet。在這種情況下,當我們修改天氣 Portlet 所選擇的城市的話,地圖以及旅遊信息的 Portlet 也會自動做出相應變化。從而實現了不同 Portlet 之間的協作。

  共享呈現參數的聲明

  對共享呈現參數的使用聲明包括兩個部分,對共享呈現參數定義的聲明和支持共享呈現參數的 Portlet 聲明。

  共享呈現參數定義聲明:對於共享呈現參數定義的聲明必須在 portlet.xml 部署文件中使用 <public-render-parameter> 關鍵字,該元素與 <portlet> 元素並列爲 <portlet-app> 的分支。

清單 16. 共享呈現參數定義聲明

<portlet-app ...> 
  <portlet> 
    <portlet-name>Portlet A</portlet-name> 
  ... 
  </portlet> 
  ... 
  <public-render-parameter> 
    <identifier>public-render-param1</identifier> 
  </public-render-parameter> 
  <public-render-parameter> 
    <identifier>public-render-param2</identifier> 
  </public-render-parameter> 
</portlet-app> 

  支持共享呈現參數 Portlet 聲明:對於支持共享呈現參數的 Portlet 的聲明需要在 portlet.xml 中 <portlet> 元素中使用 <supported-public-render-parameter> 關鍵字。

清單 17. 共享呈現參數 Portlet 定義聲明

<portlet> 
  <portlet-name>Portlet B</portle-name> 
  ...... 
  <supported-public-render-parameter> 
    <identifier>public-render-param1</identifier> 
  </supported-public-render-parameter> 
</portlet>   
 
<portlet> 
  <portlet-name>Portlet C</portle-name> 
  ...... 
  <supported-public-render-parameter> 
    <identifier>public-render-param2</identifier> 
  </supported-public-render-parameter> 
</portlet> 

  共享呈現參數的使用

  與非共享呈現參數的使用方法相同,共享呈現參數可以通過 ActionResponse 的 setRenderParameter("標識","值") 方法設定,並通過 RenderRequest 的 getParameter("標識") 來獲得。見 清單 18 和清單 19

清單 18. Portlet A 設定共享呈現參數

... 
public void processAction(ActionRequest actionRequest, 
    ActionResponse actionResponse) throws PortletException, IOException { 
  String publicRenderParamValue1 = actionRequest.getParameter("public-render-param1"); 
  actionResponse.setRenderParameter("public-render-param1", publicRenderParamValue1); 
} 
... 

清單 19. Portlet B 獲取共享呈現參數

... 
public void render(RenderRequest renderRequest, 
    renderResponse renderResponse) throws PortletException, IOException { 
  ... 
  String publicRenderParamValue1 = renderRequest.getParameter("public-render-param1"); 
  ... 
} 
...... 

  Portlet 過濾器

  Portlet 過濾器是 JSR 286 提供的有一個非常重要的新特性。事實上,在 JSR 286 之前,就已經有很多廠商(包括 IBM)自定義擴展了 JSR168,提供了過濾器功能。由此可見,Portlet 過濾器的重要性。爲了避免各種廠商不同 Portlet 過濾器的不兼容性,JCP(JavaCommunity Process)對 JSR 286 定義了標準的過濾器實現。

  什麼是 Portlet 過濾器?

  與 Servlet 相似,Portlet 過濾器可以使用戶可以改變一個 request 和修改一個 response。Filter 不是一個 Portlet,它不能產生一個 response,它能夠在一個 request 到達 Portlet 之前預處理 request,也可以在離開 Portlet 時處理 response。換句話說,過濾器其實是一個“Portlet chaining”(Portlet 鏈)。它能夠實現的功能包括:

  在 Portlet 被調用之前截獲;

  在 Portlet 被調用之前檢查 servlet request;

  根據需要修改 request 頭和 request 數據;

  根據需要修改 response 頭和 response 數據;

  在 Portlet 被調用之後截獲;

  Portlet 過濾器與 Servlet 過濾器

  事實上,從宏觀功能的角度看來,Portlet 過濾器和 Servlet 過濾器是很相似的。這是因爲二者都可以聲明性地嵌入,從而截獲並修改請求和響應。但是理解它們之間存在着很大的不同是非常重要的。在一定程度上,它們之間的差異是與 Servlet 和Portlet 之間的差異相聯繫的:Servlet 過濾器是一個門戶級過濾器,它可以修改由一些小的部分(來自頁面上所有 Portlet 的響應)集合而成的整個門戶頁面;而 Portlet 過濾器只能用於那些小的部分。Servlet 過濾器(如果已經安裝的話)是接收和修改客戶端請求的第一個組件,同時也是修改對客戶端的響應的最後一個組件(請參見圖 3)。

圖 3. 帶有 Servlet 過濾器和 Portlet 過濾器的客戶端請求事件序列

  查看原圖(大圖)

  如圖 3 所示,Servlet 請求(步驟 1)在分派給一個或多個 Portlet 請求(步驟 3)之前首先通過一個 Servlet 過濾器鏈進行處理(步驟 2)。在結果集聚到一起(步驟 6 和 7)之前,Portlet 請求進一步轉發到 Portlet 過濾器鏈進行處理(步驟 4)。接着,將集聚的結果發送回Servlet 過濾器進行處理,之後,將集聚的結果最終顯示給用戶(步驟 9)。

  另外一點需要注意的是,Servlet 過濾器比 Portlet 過濾器的優先級別要高,容器將首先進行 Servlet 過濾,其次是 Portlet 過濾。一個過濾器鏈包含一個或多個過濾器。在一個過濾器完成處理之後,新的請求和響應將傳送到鏈上的下一個過濾器;鏈上的最後一個過濾器調用目標資源(Servlet 或 Portlet)。

  Portlet 過濾器的工作原理

  Portlet 過濾器可以放置在 V2.0 規範提供的任何生命週期方法調用的前面或者後面(processAction、processEvent、render、serveResource),而且還支持這些生命週期方法使用包裝的請求和響應。與 Servlet 過濾器類似,Portlet 過濾器需要在 portlet.xml 部署描述符中進行定義,不同的是,servlet只有一個service()的請求處理方法,因此servlet只有一種類型的過濾器。而portlet 卻有四種請求處理方法,於是有四種類型的過濾器,包括:

  Action 過濾器

  Render 過濾器

  Resource 過濾器

  Event 過濾器

  下面我們來介紹四種過濾器的工作原理。

  JSR 286 爲 Portlet 過濾器提供了接口類 PortletFilter,該接口提供了兩個方法。

  void init(FilterConfig config)
容器調用一次這個方法來準備用於服務的過濾器。對象 filterConfig 使得過濾器能夠訪問配置參數以及對門戶上下文的引用。

  void destroy()
這個方法是在將過濾器從服務移除之後調用的。這個方法使得過濾器能夠清除任何存放的資源。

  JSR 286 爲四種過濾器分別定義了一個接口類,這四個接口類都繼承 PortletFilter 類,並分別添加了各自 doFilter() 方法。關於這幾個類之間的關係,請見下圖:
圖 4. Portlet 過濾器繼承圖

  查看原圖(大圖)

  四種過濾器分別對 Portlet 的四個方法進行攔截。用戶自定義的過濾器必須實現相應的過濾器接口,通過其 doFilter() 方法來實現相應的動作。其對應關係見表 1:

表 1. 過濾器與攔截方法關係圖

過濾器

攔截方法

Action 過濾器

processAction(ActionRequest request, ActionResponse response)

Render 過濾器

render(RenderRequest request, RenderResponse response)

Resource 過濾器

serveResource(ResourceRequest request, ResourceResponse response)

Event 過濾器

processEvent(EventRequest request, EventResponse response)



  Portlet 過濾器的部署聲明

  下面我們通過一個具體的 Portlet 過濾器部署實例來說明。參見清單 20:
清單 20. Portlet過濾器聲明

<portlet-app ...> 
  ... 
  <filter> 
    <filter-name>TestRenderFilter</filter-name>   
    <filter-class>com.ibm.jsr286.TestRenderFilter</filter-class> 
    <lifecycle>RENDER_PHASE</lifecycle> 
  </filter> 
 
  <filter> 
    <filter-name>TestAllFilter</filter-name> 
    <filter-class>com.ibm.jsr286.TestAllFilter</filter-class> 
    <lifecycle>ACTION_PHASE</lifecycle> 
    <lifecycle>RENDER_PHASE</lifecycle> 
    <lifecycle>EVENT_PHASE</lifecycle> 
    <lifecycle>RESOURCE_PHASE</lifecycle> 
  </filter> 
 
  <filter-mapping>   
    <filter-name>TestRenderFilter</filter-name> 
    <portlet-name>DocumentPortlet</portlet-name> 
    </filter-mapping> 
 
  <filter-mapping>   
    <filter-name>TestAllFilter</filter-name> 
    <portlet-name>Test*</portlet-name> 
  </filter-mapping> 
</portlet-app> 

  關於 Portlet 過濾器有幾點需要聲明的是:

  對於一個 Portlet 過濾器的聲明亦包括兩部分,過濾器的定義聲明以及過濾器的映射聲明。

  一個 Portlet 過濾器可以爲多個 Portlet 服務,而且 一個 Portlet 可以同時有多個 Portlet 過濾器。

  一個 Portlet 過濾器可以有多個生命週期階段,當然前提是該 Portlet 過濾器實現了相應過濾器接口。

  Portlet 窗口

  Portlet 窗口在 JSR 168 中僅間接地得到反映,並作爲容器爲 Portlet 範圍的會話條目生成的前綴的一部分。JSR 286 規範現在使該 Portlet 窗口 ID 可通過請求供 Portlet使用,簡單的說就是,PortletRequest新增了一個方法getWindowID(),可以獲得 Portlet 的窗口 ID,這個 ID 是由容器生成的。在Portal 容器中佈局同一個 Portlet 多次的情況下,windowID 可以用來區分同一個 Portlet 的不同窗口,從而可以使這些 Portlet 窗口緩存並呈現不同的數據。

Portlet 窗口支持的一個常見用例是在 Portlet 中緩存數據;例如,一個 Portlet,呈現所使用的數據是通過 web services 從遠端服務器獲得,而且網絡調用速度比較慢,且呈現的數據爲只讀,這種情況下就可以把 web services 獲得的數據和窗口 ID 關聯並緩存起來,在頻繁調用Portlet 的時候,就不必頻繁等待 web services 的返回。

 

 

·       第二部分 資源服務、事件與共享呈現參數

======================================================================

本文將首先介紹 JSR 286 參考實現 Apache Pluto 2.0 平臺的構建過程,然後通過在 Apache Pluto 2.0 平臺上開發和部署 JSR 286 Portlet 應用程序, 向讀者介紹 JSR 286 Portlet 資源服務和新增的交互功能:事件和共享呈現參數。

       

  在本系列的 第 1 部分簡要回顧了 JSR 168 Portlet,並對 JSR 286 Portlet 的新增特性做了詳細的介紹, 本文將通過在 Apache Pluto 2.0 平臺上開發和部署 Portlet 應用程序, 向讀者介紹 JSR 286 Portlet 新特性的使用方法。本文將首先介紹 JSR 286 參考實現 Apache Pluto 2.0 平臺的構建過程,然後通過在 Apache Pluto 2.0 平臺上開發和部署 JSR 286 Portlet 應用程序, 向讀者介紹 JSR 286 Portlet 資源服務和新增的交互功能:事件和共享呈現參數。

  關於本系列

  本系列 專門針對具有 JSR 168 Portlet 開發基礎,並且想了解 JSR 286 Portlet 新特性和開發流程的開發人員。在學完本系列後,您將瞭解到相對於 JSR168 Portlet,JSR 286Portlet 究竟提供了哪些增強功能, 以及這些新增特性在實際開發中的應用。

  本系列的 第 1 部分簡單回顧了 JSR 168 Portlet, 並列出了 JSR 286 Portlet 的新增內容。第 2 部分和第 3 部分將通過在 Apache Pluto 2.0 平臺上開發和部署 Portlet 應用程序, 向讀者介紹 JSR 286 Portlet 新特性的使用方法。

  關於本文

  本文將首先介紹 JSR 286 參考實現 Apache Pluto 2.0 平臺的構建過程,然後通過在 Apache Pluto 2.0 平臺上開發和部署 JSR 286 Portlet 應用程序, 向讀者介紹 JSR 286 Portlet 資源服務和新增的交互功能:事件和共享呈現參數。

  Portlet 過濾器和 Portlet 窗口方面應用程序的開發過程,將在第 3 部分進行詳細介紹。

在示例應用程序的開發和部署中用到了下列產品:

  Sun JDK 1.5

  Apache Tomcat 6.x

Apache Pluto 2.0

Apache Maven 2.x

  Eclipse Europa(Eclipse V3.3) for JavaEE Developers

  閱讀本文之前,您應當對 JSR 168 Portlet 有所瞭解,並閱讀了本系列的第 1 部分。

  準備工作

  Apache Pluto 2.0 是 JSR 286 的參考實現,是實現了 Portlet 2.0 API 的 Portlet 容器,充當 Portlet 的運行時環境,與 web 應用服務器的 Servlet容器的運行時環境支持Servlet 的情形非常相似。Pluto2.0 目前支持的 JSR 286Portlet 新特性有資源服務、事件、Portlet過濾器、共享呈現參數、Portlet 窗口。

  在本文中,我們將使用 Apache Pluto 2.0 開發測試我們的 JSR 286 Portlet 應用程序。以下操作均在 Windows XP操作系統環境下進行。

  1. 構建 JSR 286Portlet 運行環境 ApachePluto 2.0

  Apache Pluto 2.0 目前還處於開發階段,我們只能通過其源代碼構建出一個支持 JSR 286 Portlet 標準的 Portlet 2.0 容器。

  安裝 Sun JDK 1.5 並設定環境變量

  該步驟一般讀者都比較熟悉,不再拗述。需要注意的是,經過筆者測試,Pluto 2.0 源碼工程只可以在 Sun JDK 1.5 下構建成功,筆者使用 Sun JDK 1.6 和 IBM JDK 1.5 均構建失敗。

  安裝 Maven 2

  Pluto 源代碼使用 Maven 2 進行項目管理和構建,我們必須首先安裝該工具。

  從 http://maven.apache.org/ 上尋找 Maven 2 的最新版本壓縮包,下載並解壓,設定 Maven 2 的安裝路徑爲 ${M2_HOME}。將 ${M2_HOME}\bin 目錄加到系統的 PATH 環境變量中。

安裝 Tomcat 6

  從 http://tomcat.apache.org/ 上尋找 Tomcat 6 的最新版本壓縮包,下載並解壓,設定安裝路徑爲 ${TOMCAT_HOME}。

  獲取 Apache Pluto 2.0 源碼

  使用 SVN 客戶端從官方 SVN 服務器上獲得源代碼:

  清單 1. 使用 SVN 客戶端從官方 SVN 服務器上獲得源代碼

  svn checkout https://svn.apache.org/repos/asf/portals/pluto/trunk/ pluto2

  使用 Maven 2 構建 Pluto 2.0

  編輯 ${M2_HOME}\conf 目錄下的 settings.xml 文件,增加 <pluginGroups> 元素:

  清單 2. settings.xml 文件

<settings> 
 ... 
 <pluginGroups> 
  <pluginGroup>org.apache.pluto</pluginGroup> 
 </pluginGroups> 
 ... 
</settings> 

  打開 pluto2 目錄下的 pom.xml 文件,找到

  清單 3. pom.xml 文件

... 
<jaxb-impl.version>2.1.2</jaxb-impl.version> 
... 

  改爲

  清單 4. pom.xml 文件

  <jaxb-impl.version>2.1.3</jaxb-impl.version> 

  命令行模式下進入 pluto2 目錄,執行以下命令:

  清單 5.

D:\>cd pluto2 
D:\pluto2>mvn install 
D:\pluto2>mvn pluto:install -DinstallDir=${TOMCAT_HOME} 

  如果您的 Tomcat 安裝路徑中存在空格,則需要用雙引號把路徑引起來:

  清單 6.

mvn pluto:install 
  -DinstallDir="C:\Program Files\Apache Software Foundation\Tomcat 6.0"

從網上尋找 commons-logging-api-1.1.jar 文件,拷貝到 ${TOMCAT_HOME}\lib\ 目錄下。

  至此,pluto2.0 的相關文件就被安裝到 tomcat 相應目錄下。

  編輯 ${TOMCAT_HOME}\conf\tomcat-users.xml 文件,添加角色 pluto,並在該角色下新增一個用戶,以下爲示例文件:

  清單 7. tomcat-users.xml 文件

<?xml version="1.0" encoding="UTF-8"?> 
<tomcat-users> 
 <role rolename="role1"/> 
 <role rolename="pluto"/> 
 <role rolename="tomcat"/> 
 <role rolename="manager"/> 
 <user password="pluto" roles="pluto,manager" username="pluto"/> 
 <user password="tomcat" roles="role1" username="role1"/> 
 <user password="tomcat" roles="tomcat,role1" username="both"/> 
 <user password="tomcat" roles="tomcat,pluto,manager" username="tomcat"/> 
</tomcat-users> 

  驗證安裝

  運行 ${TOMCAT_HOME}\bin\startup.bat,啓動 tomcat 服務器。瀏覽器訪問 URL http://localhost:8080/pluto/portal,如圖 1 所示:

  圖 1. Pluto 登錄界面

    查看原圖(大圖)

  輸入添加到 pluto 角色的用戶名和密碼,進入 Pluto 的 Portal 頁面:

  圖 2. Pluto Portal界面

    查看原圖(大圖)

  至此,JSR 286 Portlet 運行環境 Apache Pluto 2.0 搭建成功。

  2. 使用 EclipseEuropa 建立開發環境

  首先,需要從 Eclipse 官方網站 http://www.eclipse.org 下載 Eclipse Europa,針對不同的開發需求,有幾種包可供下載。我們進行的是 J2EE Web 開發,所以注意要下載 Eclipse IDE for Java EE Developers。

  啓動 Eclipse,對 Eclipse 進行配置:

  執行菜單項目 Window -> Preferences,打開 Preferences 對話框,選擇 Server -> Installed Runtimes 項,如圖 3 所示:

  圖 3. Preferences 對話框

    查看原圖(大圖)

  點擊 Add 按鈕,將 Tomcat 6 添加爲運行時,如 圖 4、圖 5 所示:

  圖 4. 選擇運行時類型

  圖 5. 設定 Tomcat 安裝路徑

  單擊 Finish 結束配置,單擊 OK 關閉 Preferences對話框。

  在 Eclipse 的 Servers 視圖中單擊鼠標右鍵,選擇 New -> Server。如 圖 6 所示:

圖 6. 新建服務器

  在彈出的窗口中選擇目標運行服務器 Apache Tomcat 6.0 Server,運行時呈現 Apache Tomcat v6.0,如圖7所示,點擊 Finish。

  圖 7. 選擇目標運行服務器

    查看原圖(大圖)

  在 Servers 視圖中雙擊剛剛新建的 Tomcat 服務器,打開服務器配置頁面,如圖 8 所示:

  圖 8. Tomcat 服務器配置頁面

    查看原圖(大圖)

  在 Server Locations 中選擇 Use Tomcat Installation,Deploy Path選擇 ${TOMCAT_HOME}\webapps,如圖 9 所示。至此開發環境設置完畢。

  必須設定 Deploy Path 爲 Tomcat 安裝目錄下的 webapps 目錄,否則使用 Eclipse 啓動 Tomcat 後,Pluto 不能加載進來。

  圖 9. Tomcat 服務器配置

  創建 JSR 286 Portlet 應用

  下面,我們開始創建一系列程序來演示 JSR 286 Portlet 的新特性。主要分爲以下六個部分:

  使用 Eclipse 創建 Portlet Web 項目

  資源服務

  事件

Portlet 過濾器

  共享呈現參數

  Portlet 窗口

  1. 使用 Eclipse 創建 Portlet Web 項目

  新建項目,項目類型選擇 Web->Dynamic Web Project,如圖 10 所示:

  圖 10. 新建動態 Web 項目

  接下來,設置項目屬性,項目名稱 jsr286portlets, 目標運行時 Apache Tomcat V6.0,保留默認設置,點擊 Finish,如 圖 11 所示:

  圖 11. 設置項目屬性

    查看原圖(大圖)

  生成項目結構如圖 12:

  圖 12. 項目結構

  在 META-INF 下新建 context.xml 文件,內容如下:

  清單 8. context.xml 文件

  <Context crossContext="true" /> 

  該文件爲 Tomcat 的特有配置文件,根據 Pluto 的要求,該 Web 工程的上下文應該可以被其它JavaEE 程序訪問,所以crossContext 參數設置爲 true。

  在 WEB-INF 下新建 portlet.xml 文件,內容如下:

  清單 9. portlet.xml 文件

<?xml version="1.0" encoding="UTF-8"?> 
<portlet-app 
  xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd 
      http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd" 
  version="2.0"> 
  <!-- 在該位置填寫portlet描述內容 --> 
</portlet-app>

下面我們將在黑體部分填寫 portlet 定義。

  2. 資源服務

  新增 Java 類 TestPortlet,實現了 javax.portlet.Portlet 和javax.portlet.ResourceServingPortlet 接口:

  清單 10. TestPortlet.java 文件

package com.ibm.samples.jsr286.portlets; 
 
import ... 
 
public class TestPortlet implements Portlet, ResourceServingPortlet { 
 
  private PortletConfig portletConfig; 
 
  public void init(PortletConfig portletConfig) throws PortletException { 
    this.portletConfig = portletConfig; 
  } 
 
  public void destroy() { 
  } 
 
  public void processAction(ActionRequest actionRequest, 
    ActionResponse actionResponse) throws PortletException, IOException { 
  } 
 
  public void render(RenderRequest renderRequest, 
    RenderResponse renderResponse) throws PortletException, IOException { 
    PortletRequestDispatcher portletRequestDispatcher = portletConfig 
      .getPortletContext().getRequestDispatcher( 
        "/WEB-INF/jsp/TestPortletView.jsp"); 
    portletRequestDispatcher.include(renderRequest, renderResponse); 
  } 
 
  public void serveResource(ResourceRequest resourceRequest, 
    ResourceResponse resourceResponse) throws PortletException, 
    IOException { 
    PortletRequestDispatcher portletRequestDispatcher = portletConfig 
      .getPortletContext().getRequestDispatcher( 
        "/WEB-INF/jsp/TestPortletResource.jsp"); 
    portletRequestDispatcher.include(resourceRequest, resourceResponse); 
  } 
} 

在 WEB-INF 目錄下新建 jsp 目錄,在 jsp 目錄下新建 portlet 呈現階段所顯示的 jsp 文件 TestPortletView.jsp。

  清單 11. TestPortletView.jsp 文件

<%@ page language="java" contentType="text/html; charset=UTF-8"  pageEncoding="UTF-8"%> 
<%@taglib prefix="portlet" uri="http://java.sun.com/portlet_2_0"%> 
<portlet:defineObjects /> 
<table> 
  <tr> 
    <td><h1>Test portlet page.</h1></td> 
  </tr> 
  <tr> 
    <td><a href="<portlet:resourceURL/>">Click me to request Resource URL</a></td> 
  </tr> 
</table> 

  在 jsp 目錄下新建 portlet 資源服務所請求的 jsp 文件 TestPortletResource.jsp。

  清單 12. TestPortletResource.jsp 文件

<%@ page language="java" contentType="text/html; charset=UTF-8"  pageEncoding="UTF-8"%> 
<%@taglib prefix="portlet" uri="http://java.sun.com/portlet_2_0"%> 
<portlet:defineObjects /> 
<table> 
  <tr> 
    <td><h1>Test portlet resource page.</h1></td> 
  </tr> 
</table> 

  編輯 portlet.xml 文件, 爲 TestPortlet 增加一個 portlet 定義片斷,該 TestPortlet 僅支持 View 模式。

  清單 13. TestPortlet 定義片斷

<portlet> 
  <portlet-name>TestPortlet</portlet-name> 
  <display-name>TestPortlet</display-name> 
  <portlet-class>com.ibm.samples.jsr286.portlets.TestPortlet</portlet-class> 
  <supports> 
    <mime-type>text/html</mime-type> 
    <portlet-mode>VIEW</portlet-mode> 
  </supports> 
  <portlet-info> 
    <title>TestPortlet</title> 
  </portlet-info> 
</portlet> 

  編輯 web.xml 文件,增加 Pluto 所需的 servlet 定義及映射。讀者請注意,該定義爲 Pluto 2.0 Portlet 容器所需,不屬於 JSR 286 標準的要求。

  清單 14. Pluto 所需的 servlet 定義及映射片斷

<servlet> 
  <servlet-name>TestPortlet</servlet-name> 
  <servlet-class> 
    org.apache.pluto.core.PortletServlet 
  </servlet-class> 
  <init-param> 
    <param-name>portlet-name</param-name> 
    <param-value>TestPortlet</param-value> 
  </init-param> 
  <load-on-startup>1</load-on-startup> 
</servlet> 
 
<servlet-mapping> 
  <servlet-name>TestPortlet</servlet-name> 
  <url-pattern>/PlutoInvoker/TestPortlet</url-pattern> 
</servlet-mapping> 

 

·                 第三部分 Portlet 過濾器和Portlet 窗口

======================================================================

在 本系列 的 第 1 部分簡要回顧了JSR 168 Portlet,並對 JSR 286 Portlet 的新增特性做了詳細的介紹,第 2 部分 和第 3 部分將通過在 ApachePluto 2.0 平臺上開發和部署Portlet 應用程序, 向讀者介紹 JSR 286 Portlet 新特性的使用方法。本文將介紹 JSR 286 Portlet 的 Portlet 過濾器和 Portlet 窗口應用程序開發。

  關於本系列

  本系列 專門針對具有 JSR 168 Portlet 開發基礎,並且想了解 JSR 286 Portlet 新特性和開發流程的開發人員。在學完本系列後,您將瞭解到相對於 JSR168 Portlet,JSR 286Portlet 究竟提供了哪些增強功能, 以及這些新增特性在實際開發中的應用。

  本系列的 第 1 部分簡單回顧了 JSR 168 Portlet, 並列出了 JSR 286 Portlet 的新增內容。第 2 部分 和第 3 部分將通過在 ApachePluto 2.0 平臺上開發和部署Portlet 應用程序,向讀者介紹 JSR286 Portlet 新特性的使用方法。

  關於本文

  本文承接 第 2 部分,繼續介紹 JSR 286 Portlet 的 Portlet 過濾器和 Portlet 窗口應用程序開發。閱讀本文之前,您應當對 JSR 168 Portlet 有所瞭解,並閱讀了本系列的第 1 部分和 第 2 部分。

  Portlet 過濾器

  通過 第 1 部分的介紹,我們知道 Portlet 過濾器分爲:

  Action 過濾器

  Render 過濾器

  Resource 過濾器

  Event 過濾器

  我們將首先對這四種 Portlet 過濾器的開發使用流程分別單獨進行介紹,然後將這四種 Portlet 過濾器綜合起來進行更進一步的開發,最後通過和 Servlet 過濾器的結合使用,使讀者明白 Portlet 過濾器和 Servlet 過濾器的關係和區別。

Action 過濾器

  新建 Java 類TestActionFilter:

  清單 1. TestActionFilter.java 文件

package com.ibm.samples.jsr286.filters; 
 
import ... 
 
public class TestActionFilter implements ActionFilter { 
 
  private static Log log = LogFactory.getLog(TestActionFilter.class); 
 
  private FilterConfig filterConfig; 
 
  public void init(FilterConfig filterConfig) throws PortletException { 
    log.info("action filter [" + filterConfig.getFilterName() 
        + "] is initialized."); 
    this.filterConfig = filterConfig; 
  } 
 
  public void destroy() { 
    log.info("action filter [" + filterConfig.getFilterName() 
        + "] is destroyed."); 
  } 
 
  public void doFilter(ActionRequest actionRequest, 
      ActionResponse actionResponse, FilterChain filterChain) 
      throws IOException, PortletException { 
    log.info("action filter [" + filterConfig.getFilterName() 
        + "] is called."); 
    filterChain.doFilter(actionRequest, actionResponse); 
  } 
}      

  這個程序的主要作用就是在 Action Filter 初始化、過濾器調用,銷燬的時候分別打印相應的信息。清單 1 中filterChain.doFilter(actionRequest, actionResponse) 需要讀者特別注意,這行代碼保證了過濾器鏈的傳遞,刪去這行代碼,則過濾器鏈將在該過濾器執行結束後終結。

編輯 portlet.xml 文件,加入如下片斷:

  清單 2. Action 過濾器定義

<portlet-app ...> 
  ... 
  <filter> 
    <display-name>TestActionFilter</display-name> 
    <filter-name>TestActionFilter</filter-name> 
    <filter-class>com.ibm.samples.jsr286.filters.TestActionFilter</filter-class> 
    <lifecycle>ACTION_PHASE</lifecycle> 
  </filter> 
  ... 
</portlet-app> 

  定義 Action 過濾器映射,可以影射到具體某個 Portlet, 或者根據模式匹配到一組 Portlet:

  清單 3. Action 過濾器映射

... 
<filter-mapping> 
  <filter-name>TestActionFilter</filter-name> 
  <portlet-name>*</portlet-name> 
</filter-mapping> 
... 

  在清單 3 的定義中,我們聲明TestActionFilter 對所有 Portlet 的 processAction 調用進行攔截。

  重啓 Web 應用程序並將TestPortlet 部署到 "TestJSR 286 Portlet Page" 頁面, 並將該頁面其它所有Portlet 移除,輸入數據,點擊Submit 按鈕觸發processAction 調用,EclipseConsole 出現如下輸出:

  清單 4. Action 過濾器調用結果

... 
2008-3-16 22:35:20 com.ibm.samples.jsr286.filters.TestActionFilter init 
信息: action filter [TestActionFilter] is initialized. 
2008-3-16 22:35:24 com.ibm.samples.jsr286.filters.TestActionFilter doFilter 
信息: action filter [TestActionFilter] is called. 
2008-3-16 22:35:24 com.ibm.samples.jsr286.filters.TestActionFilter destroy 
信息: action filter [TestActionFilter] is destroyed. 
... 

從上面的信息可以看出,對於 Portlet 的每次 processAction 調用,Action Filter 都要經歷一個初始化、過濾方法 doFilter 調用、銷燬的全過程。讀者可以多次實驗證實這一點。

  Render 過濾器

  新建 Java 類TestRenderFilter:

  清單 5. TestRenderFilter.java 文件

package com.ibm.samples.jsr286.filters; 
 
import ... 
 
public class TestRenderFilter implements RenderFilter { 
 
  private static Log log = LogFactory.getLog(TestRenderFilter.class); 
 
  private FilterConfig filterConfig; 
 
  public void init(FilterConfig filterConfig) throws PortletException { 
    log.info("render filter [" + filterConfig.getFilterName() 
        + "] is initialized."); 
    this.filterConfig = filterConfig; 
  } 
 
  public void destroy() { 
    log.info("render filter [" + filterConfig.getFilterName() 
        + "] is destroyed."); 
  } 
 
  public void doFilter(RenderRequest renderRequest, 
      RenderResponse renderResponse, FilterChain filterChain) 
      throws IOException, PortletException { 
    log.info("render filter [" + filterConfig.getFilterName() 
        + "] is called."); 
    filterChain.doFilter(renderRequest, renderResponse); 
  } 
} 

這個程序的主要作用就是在 Render Filter 初始化、過濾器調用,銷燬的時候分別打印相應的信息。和 Action Filter 一樣,讀者同樣需要注意過濾鏈傳遞的問題。

  編輯 portlet.xml 文件,加入如下片斷:

  清單 6. Render 過濾器定義

<portlet-app ...> 
  ... 
  <filter> 
    <display-name>TestRenderFilter</display-name> 
    <filter-name>TestRenderFilter</filter-name> 
    <filter-class>com.ibm.samples.jsr286.filters.TestRenderFilter</filter-class> 
    <lifecycle>RENDER_PHASE</lifecycle> 
  </filter> 
  ... 
</portlet-app> 

  定義 Render 過濾器映射,可以影射到具體某個 Portlet, 或者根據模式匹配到一組 Portlet:

  清單 7. Render 過濾器映射

... 
<filter-mapping> 
  <filter-name>TestRenderFilter</filter-name> 
  <portlet-name>*</portlet-name> 
</filter-mapping> 
... 

  在清單 7 的定義中,我們聲明TestRenderFilter 對所有 Portlet 的 render 調用進行攔截。

  重啓 Web 應用程序並將多個 Portlet部署到 "TestJSR 286 Portlet Page"頁面, 訪問該頁面,EclipseConsole 出現多個如下輸出:

  清單 8. Render 過濾器調用結果

... 
2008-3-16 22:53:11 com.ibm.samples.jsr286.filters.TestRenderFilter init 
信息: render filter [TestRenderFilter] is initialized. 
2008-3-16 22:53:11 com.ibm.samples.jsr286.filters.TestRenderFilter doFilter 
信息: render filter [TestRenderFilter] is called. 
2008-3-16 22:53:11 com.ibm.samples.jsr286.filters.TestRenderFilter destroy 
信息: render filter [TestRenderFilter] is destroyed. 
... 

從上面的信息可以看出,對於 Portlet 的每次 render 調用,Render Filter 都要經歷一個初始化、過濾方法 doFilter 調用、銷燬的全過程。以上信息出現的次數與部署到頁面上的 Portlet 個數相同,意味着 Portlet 過濾器的攔截是分別針對每個 Portlet 進行的。

  Resource 過濾器

  新建 Java 類TestResourceFilter

  清單 9. TestResourceFilter.java 文件

package com.ibm.samples.jsr286.filters; 
 
import ... 
 
public class TestResourceFilter implements ResourceFilter { 
 
  private static Log log = LogFactory.getLog(TestResourceFilter.class); 
 
  private FilterConfig filterConfig; 
 
  public void init(FilterConfig filterConfig) throws PortletException { 
    log.info("resource filter [" + filterConfig.getFilterName() 
        + "] is initialized."); 
    this.filterConfig = filterConfig; 
  } 
 
  public void destroy() { 
    log.info("resource filter [" + filterConfig.getFilterName() 
        + "] is destroyed."); 
  } 
 
  public void doFilter(ResourceRequest resourceRequest, 
      ResourceResponse resourceResponse, FilterChain filterChain) 
      throws IOException, PortletException { 
    log.info("resource filter [" + filterConfig.getFilterName() 
        + "] is called."); 
    filterChain.doFilter(resourceRequest, resourceResponse); 
  } 
} 

這個程序的主要作用就是在 Resource Filter 初始化、過濾器調用,銷燬的時候分別打印相應的信息,讀者同樣需要注意過濾鏈傳遞的問題。

  編輯 portlet.xml 文件,加入如下片斷:

  清單 10. Resource 過濾器定義

<portlet-app ...> 
  ... 
  <filter> 
    <display-name>TestResourceFilter</display-name> 
    <filter-name>TestResourceFilter</filter-name> 
    <filter-class>com.ibm.samples.jsr286.filters.TestResourceFilter</filter-class> 
    <lifecycle>RESOURCE_PHASE</lifecycle> 
  </filter> 
  ... 
</portlet-app> 

  定義 Resource 過濾器映射,可以影射到具體某個 Portlet, 或者根據模式匹配到一組 Portlet:

  清單 11. Resource 過濾器映射

... 
<filter-mapping> 
  <filter-name>TestResourceFilter</filter-name> 
  <portlet-name>*</portlet-name> 
</filter-mapping> 
... 

  在清單 11 的定義中,我們聲明TestResourceFilter 對所有 Portlet 的 serveResource 調用進行攔截。

  重啓 Web 應用程序並將TestPortlet 部署到 "TestJSR 286 Portlet Page"頁面, 訪問該頁面, 點擊超鏈接“Click me to request ResourceURL”請求資源,EclipseConsole 出現如下輸出:

  清單 12. Resource 過濾器調用結果

... 
2008-3-17 13:21:03 com.ibm.samples.jsr286.filters.TestResourceFilter init 
信息: resource filter [TestResourceFilter] is initialized. 
2008-3-17 13:21:03 com.ibm.samples.jsr286.filters.TestResourceFilter doFilter 
信息: resource filter [TestResourceFilter] is called. 
2008-3-17 13:21:05 com.ibm.samples.jsr286.filters.TestResourceFilter destroy 
信息: resource filter [TestResourceFilter] is destroyed. 
... 

從上面的信息可以看出,對於 Portlet 的每次 serveResource 調用,Resource Filter 都要經歷一個初始化、過濾方法 doFilter 調用、銷燬的全過程。

  Event 過濾器

  新建 Java 類TestEventFilter

  清單 13. TestEventFilter.java 文件

package com.ibm.samples.jsr286.filters; 
 
import ... 
 
public class TestEventFilter implements EventFilter { 
 
  private static Log log = LogFactory.getLog(TestEventFilter.class); 
 
  private FilterConfig filterConfig; 
 
  public void init(FilterConfig filterConfig) throws PortletException { 
    log.info("event filter [" + filterConfig.getFilterName() 
        + "] is initialized."); 
    this.filterConfig = filterConfig; 
  } 
 
  public void destroy() { 
    log.info("event filter [" + filterConfig.getFilterName() 
        + "] is destroyed."); 
  } 
 
  public void doFilter(EventRequest eventRequest, 
      EventResponse eventResponse, FilterChain filterChain) 
      throws IOException, PortletException { 
    log.info("event filter [" + filterConfig.getFilterName() 
        + "] is called."); 
    Event event = eventRequest.getEvent(); 
    log.info("event name: " + event.getName()); 
    log.info("event qname: " + event.getQName()); 
    log.info("event value: " + event.getValue().toString()); 
    filterChain.doFilter(eventRequest, eventResponse); 
  } 
 
} 

這個程序的主要作用就是在 Event Filter 初始化、銷燬的時候分別打印相應的信息,在 doFilter 方法中,截獲事件的名稱、QName 和 事件值,讀者同樣需要注意過濾鏈傳遞的問題。

  編輯 portlet.xml 文件,加入如下片斷:

  清單 14. Event 過濾器定義

<portlet-app ...> 
  ... 
  <filter> 
    <display-name>TestEventFilter</display-name> 
    <filter-name>TestEventFilter</filter-name> 
    <filter-class>com.ibm.samples.jsr286.filters.TestEventFilter</filter-class> 
    <lifecycle>EVENT_PHASE</lifecycle> 
  </filter> 
  ... 
</portlet-app> 

  定義 Event 過濾器映射,可以影射到具體某個 Portlet, 或者根據模式匹配到一組 Portlet:

  清單 15. Event 過濾器映射

... 
<filter-mapping> 
  <filter-name>TestEventFilter</filter-name> 
  <portlet-name>*</portlet-name> 
</filter-mapping> 
... 

  在清單 15 的定義中,我們聲明TestEventFilter 對所有 Portlet 的 processEvent 調用進行攔截。

  重啓 Web 應用程序並將TestSimpleEventSenderPortlet、TestSimpleEventReceiverPortlet、 TestComplexEventSenderPortlet、TestComplexEventReceiverPortlet 部署到 "Test JSR 286 Portlet Page" 頁面, 訪問該頁面,點擊 TestSimpleEventSenderPortlet 按鈕,Eclipse Console 出現如下輸出:

清單 16. Event 過濾器調用結果

... 
2008-3-17 19:51:35 com.ibm.samples.jsr286.filters.TestEventFilter init 
信息: event filter [TestEventFilter] is initialized. 
2008-3-17 19:51:35 com.ibm.samples.jsr286.filters.TestEventFilter doFilter 
信息: event filter [TestEventFilter] is called. 
2008-3-17 19:51:35 com.ibm.samples.jsr286.filters.TestEventFilter doFilter 
信息: event name: simple-event 
2008-3-17 19:51:35 com.ibm.samples.jsr286.filters.TestEventFilter doFilter 
信息: event qname: {http://cn.ibm.com/}simple-event 
2008-3-17 19:51:35 com.ibm.samples.jsr286.filters.TestEventFilter doFilter 
信息: event value: simple-event is sent by TestSimpleEventSenderPortlet 
2008-3-17 19:51:35 com.ibm.samples.jsr286.filters.TestEventFilter destroy 
信息: event filter [TestEventFilter] is destroyed. 
... 

  從上面的信息可以看出,對於 Portlet 的每次發送事件行爲,Event Filter 都要經歷一個初始化、過濾方法 doFilter 調用、銷燬的全過程。從清單 16 也可以看到過濾器捕獲到的事件信息。

  發送複雜事件的過濾器結果捕獲讀者可以自行測試。

  綜合使用 Portlet 過濾器

  Portlet 的四種過濾器可以集成到一個類中去實現,只要該類實現了上述四個接口即可。以下爲類 TestAllPhaseFilter 的類圖:

  圖 1. TestAllPhaseFilter 的繼承關係

 

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