基於事件偵聽與狀態模式轉換的Portlet開發
1.1 概念與前提
要讀懂這節內容,並學會使用狀態模式開發Portlet,你必須具備這裡提到的幾種設計思路,並具備基本的Java開發技能。這裡我們選用的開發工具是 IBM Rational Application Developer以及Portlet Toolkit,你需要熟悉該工具,並懂得如何建立Portlet和Portlet所需的包、類、頁面,以及執行環境所需的.jar。
1.1.1 狀態模式應用於Portlet
StateManagerPortlet是主要的Portlet類,並且是Portlet無關的。它用作分派器以支援駐留著Portlet程式碼的操作和狀態類
實現狀態介面的類將實現一個performView方法,該方法是由 StateManagerPortlet 的 service 方法呼叫的。該方法還包含通常駐留在這些方法中的程式碼。同樣,這段程式碼也是特定於它所駐留的狀態類的,因此就避免了確定請求的操作帶來的所有額外的控制邏輯混亂。
應用狀態模式會使實現變得簡潔。而且,當在Portlet的各種模式之間切換時,狀態總是被記住的。有了這種模式,你就可以輕鬆地確定想要何時以及在何處從源檢索資料
1.1.2 Portlet為什麼使用狀態模式
考慮到簡單的基於 MVC 的 Portlet,它從遠端機器上將包含一個產品下載配置引數列表的XML檔案拷貝到本地(Portal系統所在的主機),檢索各項引數,並把列表呈現給使用者。在使用者選擇了某一項後將顯示詳細的檢視,該檢視顯示的是被選擇項的詳細資訊。這個詳細檢視可能涉及向資料庫再次發出請求。
第一步,控制器必須呼叫業務物件來建立合適的Bean,該Bean被傳遞給JSP以用於主檢視中的顯示。第二步,控制器在使用者的請求中檢索被選擇項的指示器,然後呼叫業務物件來建立合適的Bean,該Bean被傳遞給不同的 JSP以用於詳細檢視中的顯示。
在 MVC實現中,檢視元件顯然是不同的。這兩種檢視需要有兩種不同的JSP。你可能需要訪問不同的業務模型元件來生成每個請求所需的Bean。控制器函式需要知道:
— 呼叫哪些業務方法?
— 生成哪些Bean?
— 把這些請求物件上的Bean放在哪裡?
— 如何呼叫合適的JSP?
這就是程式碼變得複雜的地方。如果使用Servlet程式設計,那麼可能選擇把它們實現為不同的Servlet,以使每個合適的Servlet 類的服務方法自然地分隔每個請求的控制器函式。如果你更喜歡使用分派器方式而不是不同的Servlet實現,那麼必須使用框架(例如Struts)來實現類似的MVC設計。
這是複雜的任務,因為不能選擇使用多個Portlet 類來實現一個 Portlet,不能直接處理 Portlet類。相同的Portlet的服務方法用來處理返回給該Portlet的所有HTTP請求,因此,需要實現控制程式碼類具有如下功能。
— 確定正在請求哪個操作?
— 需要進行哪些處理?
— 使Portlet 處於哪種狀態?
— 顯示什麼內容返回給使用者?
1.2 需求分析
1.2.1 Portlet功能需求
我們在Linux主機 test.cn.ibm.com上執行著一個自動下載的程式,要下載的產品列表及各產品的引數儲存在 /home/isc/downLoadConfig/下,檔名為AutodownConfig.xml。我們在Windows 2000 Server 主機Netecauto01上執行著IBM WebSphere Portal 5.1,普通使用者只能檢視產品列表及配置許可權,當該使用者要檢視配置資訊時,Portlet 能從伺服器上將檔案AutodownConfig.xml拷貝到本地伺服器,並讀出XML檔案中的內容,顯示在列表中。具有管理許可權的使用者還有修改各配置資訊的許可權,能對產品配置資訊的各項引數進行修改。修改完成後,要使該配置有效,必須將該XML檔案回傳到pvcent07的存放目錄。
AutoDownLoadConfig Portlet 維護一個自動下載的產品配置列表,在編輯模式下,使用者可以瀏覽並下載產品列表,檢視一個選定的下載產品的詳細資訊,也可以修改一個產品的配置資訊。在配置模式下,使用者可以設定資料訪問資訊。雖然可用的資料來源提供了包括配置模式在內的完整的實現,但為了滿足設計目的,我們只需注意檢視和編輯模式的實現即可。自動下載Portlet狀態模式示意圖如圖1-1所示。
圖1-1 自動下載Portlet狀態模式示意圖
1.2.2 基於MVC模型的角度架構邏輯
1.以基於MVC架構的思路整理Portlet的邏輯
從可視的角度看,圖1-1所示的場景功能的實現至少需要如下頁面。
— 主檢視頁面(Main View Page)——顯示所有正在下載的產品列表和用來選擇一個產品以獲取更多詳細資訊的選項。
— 詳細資訊檢視頁面(Detail View Page)——顯示被選擇產品的詳細配置資訊。
— 主編輯頁面(Main Edit Page)——顯示使用者有許可權編輯的產品列表和用於修改更多資訊的選項。
— 修改條目頁面(Modify Entry Page)——顯示相似的表單,其中插入了現有的資料用於修改,包括修改單項引數,以及給某一引數增刪條目列表。
在這種情況下,使用者可以從主編輯頁面中選擇需編輯的產品條目。修改後,沒有確認頁面或成功執行的頁面。進行條目修改處理並重新整理主編輯頁面,若出錯,則顯示適當的訊息,但在正常處理時,沒有與該操作關聯的檢視。
如何來改進這個實現呢?首先,請記住這個應用程式表示一組應用程式操作和狀態;其次操作是實現操作介面的類,這個類實際完成某個應用程式任務或操作處理。這就是目前存在於 actionListener 類的 actionPerformed 方法中的應用程式程式碼的一部分,只有這部分特定於單個操作事件。
狀態是實現狀態介面的類,這個類表示由於應用了操作而產生的 Portlet 的效果。一般來說,這個類有可視元件。
2.Portlet所包含的內容
根據以上分析,我們將有下面這些操作。
— 所有產品配置資訊的主列表檢視。
— 顯示選定產品的配置詳細資訊檢視。
— 顯示所有可編輯產品的編輯檢視列表。
— 顯示檢視來修改一個產品的配置資訊。
— 為產品新增一個可選的下載項或郵件。
— 為產品刪除一個可選的下載項或郵件。
執行這些操作會產生下列狀態之一。
— 主檢視。
— 詳細資訊檢視。
— 主編輯檢視。
— 新增下載產品檢視。
— 修改下載產品檢視。
從可視角度看,下載產品列表 Portlet 的實現包括如下檢視(頁面)。
(1)主檢視(Main View)——顯示下載產品列表,並帶有選項來選擇下載產品以檢視更多的資訊。
(2)詳細資訊檢視(Detail View)——顯示選定的下載產品的詳細資訊。
(3)主編輯檢視(Main Edit View)——顯示下載產品列表,並帶有選項來新增、刪除和修改下載產品資訊。
(4)新增下載產品檢視(Add Contact View)——顯示錶單檢視來輸入新下載產品的資訊。
(5)修改下載產品檢視(Modify Contact View)——顯示相似的表單檢視來新增現有的下載產品的元素資料以供修改。
1.3 Portlet詳細設計
1.3.1 程式流程設計
下面以圖示的方式來介紹程式的資料流轉。
在顯示模式下,Portlet會從遠端pvcent07.cn.ibm.com拷回XML檔案,讀出並顯示所有正在下載的產品列表。單擊列表中的任意項,將顯示該項的詳細檢視。我們先來看產品下載列表,如圖1-2所示。
圖1-2 產品下載列表
單擊任意產品的連結後,將顯示該產品的具體配置引數,如圖1-3所示。
圖1-3 產品的具體配置引數
在編輯模式下,首先顯示的是使用者有許可權編輯的產品配置列表,如圖1-4所示。
圖1-4 使用者有許可權編輯的產品配置列表
單擊任意產品的連結,進入到該產品的編輯介面,如圖1-5所示。
圖1-5 產品的編輯介面
使用者可以為該產品新增CD介質,或者新增一個E-mail,如圖1-6所示。
圖1-6 為產品新增要下載的CD介質
當然,使用者也可以刪除要下載的CD介質或者E-mail,如圖1-7所示。
圖1-7 刪除要下載的CD介質
使用者可以修改任意引數,然後單擊“Save To XML”按鈕,Portlet將把這些引數儲存到XML檔案,並傳回到pvcent機器,如圖1-8所示。
圖1-8 將修改儲存到Portlet配置檔案
確認修改儲存成功,如圖1-9 所示。
圖1-9 確認修改儲存成功
1.3.2 Actions 及 States總體設計
1.StateManagerPortlet
StateManagerPortlet是主要的Portlet類,是Portlet無關的,一般包括所有特定於 Portlet 的程式碼。該類被用作分派器以支援駐留著Portlet 程式碼的操作和狀態類。StateManagerPortlet實現了actionPerformed、doView、doEdit、doHelp和doConfigure方法。
actionPerformed方法只是獲取操作類的當前例項,然後分派到它的actionPerformed方法中。類似地,do方法獲取當前的狀態物件,然後分派到它的perform方法中。所以,StateManagerPortlet 不需要知道當前Portlet實現的任何細節,也沒有大量用來確定下一步處理的if和檢查。只要你熟悉狀態與操作之間的流程,處理就能正確地進行,而不必編寫太多的、多餘的控制邏輯程式碼。
2.ActionClassManager
在WebSphere Portal中,ActionClassManager方法把Action類例項新增到已棄用的PortelURI中。這個API是有用的,因為你可以從PortletEvent物件中檢索Action子類例項,並把該例項作為狀態轉換的一部分分派到它的actionPerformed方法中。推薦的API將使用字串而不是Action來新增到PortletURI,並且從Portlet Event 中進行檢索。ActionClassManager類提供了從給定的字串到Action 類的例項對映。
— Action
實現這個介面的類將實現actionPerformed方法,該方法可執行任何操作以實現操作請求所需的函式。但是,實現的函式特定於正被呼叫的操作事件。某個操作類的個別的actionPerformed方法僅包含該操作的程式碼,該方法還為進一步的處理設定當前狀態。在這個流程中,操作被呼叫後,它進行特定於其函式的工作,然後為下一次轉換設定狀態。
— InitialStateManager
該類為支援的每個 Portlet方式提供初始狀態。
— State
一般來說,State的perform方法將呼叫JSP來顯示它的結果。使用者介面可能讓使用者來設定Portlet中的其他操作。JSP使操作類與頁面上的每個操作關聯。在使用者呼叫其中的一個操作時,StateManagerPortlet的actionPerformed方法將呼叫合適的操作類例項,接著發生了狀態轉換。狀態類並不負責狀態管理和轉換。
實現這個介面的類需實現perform方法,該方法可被StateManagerPortlet的do方法呼叫,它包含一般駐留在這些方法中的程式碼。同樣,這些程式碼特定於它們所在類的狀態,從而降低了複雜性。
1.3.3 程式碼類設計、操作類清單
我們所建立的各個類的功能分別簡單介紹如下。
1.State類功能邏輯介紹
子類1:ActionClassManager類
ActionClassManager類負責返回一個操作子類例項,該例項給出一個類識別符號,這個識別符號是通過 addAction(字串)方法新增到 PortletURI中的。我們遵循如下約定:使類識別符號為全限定類名,這樣我們就能夠很容易地建立一個類的例項。然而,我們可以通過單獨處理操作子類例項來避免不必要的物件建立,然後需要ActionClassManager為所引用的操作子類返回單一的例項。
子類2:ActionPerformed類
抽象的操作類定義了兩個抽象的方法,因此它的子類必須實現 actionPerformed 方法和 setState 方法。當 StateManagerPortlet 類從它的 actionPerformed 方法分派處理時,操作子類的 actionPerformed 方法得到呼叫。
setState方法也可由 StateManagerPortlet呼叫,確保操作子類的實現在該使用者請求的操作處理完成之後設定下一個狀態。操作類實現它自己的setState方法,子類必須呼叫該方法來真正設定下一個狀態。這確保了操作類和狀態類知道在哪個類設定和恢復下一個狀態,而不需要子類知道這些機制。這些狀態跨HTTP請求,並維持在每種Portlet模式下。所以,當用戶改變模式時,控制返回到最近一次訪問的狀態。
子類3:MainViewAction操作類
MainViewAction 類並不需要任何特定的操作處理。事實上,我們需要將下載產品列表顯示在主檢視上。我們能夠實現在actionPerformed方法中建立恰當的下載產品列表Bean的程式碼,將該程式碼設定在會話或請求物件裡,然後將下一個狀態設定到MainViewState裡。子類 performView implementation繼承MainViewAction操作類,能夠簡單地呼叫JSP,並提交來自會話或請求的Bean裡的列表內容。然而,我們必須注意門戶頁面更新,以防更新時呼叫的是Portlet的MainViewState、performView方法,而非MainViewAction的actionPerformed方法。所以,我們不能只是將Bean放置於請求物件裡,而沒有讓 performView 方法重新建立該Bean,並將新的Bean放在該請求物件裡。我們可以在會話裡再次使用該Bean。但如果那樣做的話,資料將會過時。如果我們在編輯模式下做了變動,就需要通知該變化,並在 State 方法裡重新整理資料Bean。為了簡化這種處理,我們會只為 MainViewState 在 State 方法裡得到資料 Bean。
所以,在 MainViewState 類裡,actionPerformed方法並沒有另外處理,只是簡單地設定了MainViewState而已。
子類4:StateManagerPortlet類
當actionPerformed 完成處理時,控制返回到門戶容器,以便其他的偵聽器通知處理程式執行。然後,門戶容器通過呼叫 StateManagerPortlet 的 service 方法繼續 Portlet 請求處理。
StateManagerPortlet 的 service 方法首先試圖檢索以前執行的操作類設定的狀態類,如果沒有發現狀態類引用(例如,最初的 Portlet 呼叫),那麼就使用一個助手類 InitialStateManager 來為每種Portlet 模式確定初始的狀態類。InitialStateManager 有一個唯一的方法叫做 getInitialState,它根據 Portlet 的當前模式返回狀態物件的一個例項。對於下載產品列表 Portlet,這個類將為檢視模式返回 MainViewState 的一個例項。
State介面有一個唯一的方法 performView,所有的狀態類都必須實現它。StateManagerPortlet 的 service 方法呼叫這個方法。
子類5:MainViewState 類
MainViewState 的 performView 方法負責在 Contacts Bean 的列表表單中獲取下載產品列表,下載產品列表儲存在資料庫裡,然後傳遞到 JSP 以便在主檢視中呈現出來。
子類6:MainViewState 類
這個子類允許使用者單擊一個下載產品條目,然後顯示該下載產品的詳細資訊檢視。
用於這個標記的 href 使用來自Portlet標記庫中的createURI標記。該標記在URIAction中獲得引數,而URIAction被我們設定為操作類中用於使用者單擊操作的事件處理方法的名稱。
子類7:actionPerformed
actionPerformed類用來檢視JSP上顯示的主要清單。使用者可以從主檢視頁面選擇特定的下載產品條目來獲得詳細資訊。我們還添加了一個錨標記將下載產品條目的名稱作為可點選的連結顯示出來,當點擊時,StateManagerPortlet的 actionPerformed 方法被DetailViewAction 類呼叫。
actionPerformed方法得到所選擇的下載產品條目的物件id,並呼叫永續性類的代理為該id獲取例項化的下載產品物件。這個下載產品物件放在State類的會話中,用來呈現詳細資訊檢視。如果該頁面由於門戶頁面重新整理而得以重新整理,那麼該Bean就將在會話中可用,既然該物件只會通過使用者使用 Portlet 來獲得更新,我們就不必擔心資料過時了。
門戶頁面重新整理的另一個關鍵之處是表單資料沒有被重新初始化。如果我們試圖獲得所選下載產品的物件id作為狀態類的請求引數,它在門戶頁面重新整理時將不可用。因此,為了使 Portlet 能正常執行,必須在剛開始時就檢索資料元素,然後將其儲存在某個地方,這樣在隨後的頁面重新整理時它才可以被引用。由於 actionPerformed 方法曾被呼叫,所以這就是存放這段程式碼的好地方。
在actionPerformed方法中保留後端資料訪問,可以確保我們在頁面重新整理時對於同一資料不需要多次訪問資料庫。當然,返回到資料來源進行資料重新整理的時間和頻率取決於Portlet的需求,以及資料本身的因素。在這種情況下,資料不是動態的,當 Portlet 頁面被重新整理時,應該對其進行快取記憶體。
最後,DetailViewState 被設定為這個Portlet的下一個狀態,對於Portlet,其中的處理將繼續。
子類8:DetailViewState 類
DetailViewState 簡單地呼叫JSP來呈現詳細資訊檢視,JSP 從會話中獲取下載產品 Bean。在UI互動介面中,當用戶單擊“OK”按鈕時,處理返回到主檢視繼續進行。MainViewAction的actionPerformed 方法通過刪除我們在 DetailViewAction 類中設定的下載產品物件 id 來簡單地刪除會話資料。
子類9:ViewProductAction類
ViewProductAction類用來顯示下載產品詳細資訊檢視的JSP頁面。流邏輯以一種組織良好的方式進行,你不需要在Portlet中使用煩冗的控制程式碼。Portlet 的編輯和配置模式也以同樣的方法實現。
2.完成Portlet實現
該應用程式的其餘部分仍遵循以上所述的開發模式。編輯模式的處理控制邏輯流和檢視模式的完全一樣。我們實現一種配置模式,它允許使用者指定資料來源來儲存下載產品資料。它將以同樣的方式實現。配置模式只有一個檢視來讓使用者訪問資料來源,另外有兩個類來檢驗資料來源和儲存資料來源為配置資料。
下面是實現狀態模式的基本步驟。
所執行的特定模式的初始狀態類由 InitialStateManager 類提供。
狀態類的 performView方法(其呼叫是由 StateManagerPortlet 分派的)進行任何必要的應用程式邏輯處理,然後呼叫它的 JSP。
通過PortletURI上的一個引數將每個使用者的操作與在其中進行事件處理的操作子類的名稱相關聯。每個使用者操作一般都指定一個不同的操作子類,該子類實現一個單一的、特定的功能。
當使用者單擊一個連結時,操作類的 actionPerformed 方法被呼叫(由於通過 StateManagerPortlet 分派而被再次呼叫)。執行操作邏輯,並設定適當的狀態類以使處理繼續進行。
當事件處理完成後,StateManagerPortlet的service方法被呼叫,並再次分派給在Action事件處理階段設定的狀態類,該狀態類執行應用程式邏輯並呼叫它的 JSP 來呈現結果。
當用戶瀏覽整個 Portlet 時,操作以這種方式繼續。
3.永續性代理
訪問資料庫中的永續性資料需要額外的 Portlet 元件。AbstractBroker 類提供了一般的JDBC資料庫訪問功能。它能夠用來獲取資料庫的DataSource和Connection;它在快取記憶體中快取DataSource,從而避免重複的、高代價的JNDI查詢;它也提供通用程式碼來執行 PreparedStatement,並關閉Connection、Statement和ResultSet。
ContactListBroker 類繼承了 AbstractBroker,它實現了特定於 Portlet 需要的資料訪問方法,如 getContact 方法和 getContactList 方法,用於儲存或刪除一個列表中的條目。同時它還有檢驗表 Schema 的功能,這樣我們就可以檢驗使用者在配置模式下指定的資料來源。
4.異常處理
我們已經為Portlet 實現了大量的異常類,基本類是AIMException,AIMWrapperException是AIMException 的子類。你可以用 AIMWrapperException 封裝其他丟擲的異常,以便在 StateManagerPortlet的service 方法中高效地修改顯示行為和管理異常處理。
AIMMessageException 是一個特殊的異常,它允許在終止處理時向 Portlet 生成一條報告性訊息或錯誤訊息。例如,可以丟擲一個 AIMMessage 異常,用一條訊息指出使用者必須先登入。
actionPerformed方法或setState方法中丟擲的異常被捕獲,並且被延遲到 service 方法。管理延遲的方法是捕獲這些方法中丟擲的所有異常,並將異常(通常封裝在一個AIMWrapperException中)放在請求物件上。當呼叫StateManagerPortlet的service方法時,它首先查詢有沒有延遲異常,如果找到了,就從此處重新丟擲並處理這些異常。
因此,所有的Portlet應用程式異常都是在 StateManagerPortlet的service方法中處理的。如果發現了異常,則在以下兩種情況中呼叫異常方法:異常訊息將在Portlet中被顯示;異常訊息以及相關聯的堆疊跟蹤資訊將被顯示。將堆疊跟蹤資訊放在 Portlet 外有助於在開發時進行除錯。由於不想向實際應用的使用者顯示這種級別的詳細資訊,所以該行為是可配置的。這是在部署描述符裡定義的 debugTrace引數。
5.建立開發環境
如果你使用WebSphere Studio來建立這個Portlet,請確保下列JAR檔案的構建路徑是可用的,以便Portlet程式碼可以順利編譯。如果你在Studio中使用Portal Toolkit並建立Portlet應用專案,那麼類路徑會自動建立。不管哪種情況,你都可以建立一個Web專案或Portlet應用專案,然後匯入下載中的WAR檔案,從而將這個Portlet應用程式載入Studio。
SERVERJDK_50_PLUGINDIR/jre/lib/rt.jar
WAS_50_PLUGINDIR/lib/dynacache.jar
WAS_50_PLUGINDIR/lib/j2ee.jar
WAS_50_PLUGINDIR/lib/servletevent.jar
WAS_50_PLUGINDIR/lib/ivjejb35.jar
WAS_50_PLUGINDIR/lib/runtime.jar
WAS_50_PLUGINDIR/lib/ras.jar
WAS_50_PLUGINDIR/lib/naming.jar
WAS_50_PLUGINDIR/lib/utils.jar
WPS_V5_PLUGINDIR/portlet-api.jar
WPS_V5_PLUGINDIR/wpsportlets.jar
WPS_V5_PLUGINDIR/wps.jar
如果你沒有使用 Portal Toolkit或者Studio,那麼可以在 <WAS_ROOT>/lib 和 <WPS_ROOT>/shared/app 中找到這些檔案。
當Portlet部署到WebSphere Portal 環境中時,所有必需的JAR檔案都要同時打包進去。你可以不做修改,而是在 Portal 中安裝 Portlet WAR 檔案。
1.3.5 用RAD實現Portlet
針對入門者,我們以圖示的方式,向大家介紹使用IBM提供的開發工具RAD(Rational Application Developer)來建立、開發、除錯、打包Portlet。
原則上我是按照安裝從始至終的次序來截圖的,但為了使層次更清晰,我們還是分為以下7個步驟來分別介紹。
1.安裝RAD,建立portlet
在Windows 2003 Server系統的“控制面板”→“高階”選項中,單擊“設定”按鈕,在出現的對話方塊中選擇“資料執行儲存”面板,單擊“新增...”按鈕,把安裝後的rationalsdp.exe和enroll.exe 新增到列表中。
開啟RAD,單擊“新建”按鈕,選擇“專案”→“Portlet專案”,然後依次輸入各項引數,包括Portlet的名稱、包名、是否