2012年10月31日 星期三

JSF handlers 和 Listeners

一、Action Handllers 和 Event Listeners

點擊一個按鈕、超連結或者變更一個表單元件內的值通常需要某些類型的動作。多年以來,GUI 桌面應用程式針對事件處理已經提供了一套相當完善的事件處理定義模型,JSF 也提供了類似的機制。此類程式與 JSF 事件處理機制的主要區別在於: JSF 事件是由 client 端觸發,但在 server 端處理。

JSF 提供兩種方式來處理事件:listeners 和 action handlers,兩者皆可以定義於 managed beans 之內。listener 接受一個 FacesEvent 物件作為參數,其回傳型態則為 void;而 action handler 不接受任何參數,其回傳值的形態則為 String。listener 的一個好處是 FacesEvent 提供了額外的資訊,例如:觸發事件的表單(form)元件。Action handler 則不知道事件來源,但能根據回傳值來啟動頁面導覽(page navigation)。

二、Action Handlers

Action Handlers 通常用來作為頁面導覽。一個常見的用法是根據業務邏輯來處理表單的提交和新頁面導覽。頁面導覽由 Action Handler 的回傳值與 faces-config.xml 中定義的「navigation-rule」來決定。

讓我們來看一個例子:

<!--login.xhtml-->
<h:inputText id="username" />
<h:inputSecret id="password" />
<h:commandButton action="#{login.submitButtonAction}" type="submit" value=""Login />

<!--faces-config.xml-->
<navigation-rule>
  <form-view-id>/login.xhtml</form-view/-id>
  <navigation-case>
    <from-action>#{login.submitButtonAction}</from-action>
    <from-outcome>success</from-outcome>
    <to-view-id>/home.jsf</to-view-id>
  </navigation-case>
</navigation-rule>

<!--login/Login managedBean-->
public Srting submitButtonAction() {
 return "success";
}


在以上的例子中,使用者填寫完表單元件中的「username」和「password」欄位,然後按下 submit 按鈕(<h:commandButton>)。在「<h:commandButton>」的 action 屬性,指定了一個在 managedBean(submitButtonAction())中的 action handler,其回傳值為「success」。在 faces-config.xml 的 navigation rule 指定使用者將會被重導至「home.jsf」,在此先假設 FacesServlet 會被對映至 web.xml 中的「*.jsf」。

三、Event Listeners

當使用者點擊一個按鈕或鏈結、改變某欄位中的值,或從清單中選擇時,應用程式必須做出回應。透過應用程式的事件註冊機制,當 JSF 使用者介面元件發出一個使用者動作信號時,應用程式就會收到該事件的通知。

兩種最常見的 Event Listeners 是 action 和值的改變,但這並不包含其他像 data-model 與 phase。Listeners 可在 managed bean 或單獨的 listener class 之中宣告。將 listeners 置於一個單獨的 class 之中,可讓我們在多個 page views 之間重複使用。如果要建立一個 listener 的 class,該 class 必須實做 ActionListener 或 ValueChangeListener 介面。如前所述,一個 listener 只能接受一個 FacesEvent object 作為唯一的參數。FacesEvent 提供關於該事件的詳細資訊,例如觸發事件的表單元件,以及 JSF 生命週期的 phaseId。FacesEvent 有兩種子介面:ActionEventValueChangeEvent

1. ActionEvent

<h:commandButton> 和 <h:commandLink> 元件支援 actionListener 屬性。讓我們看一個例子:

<!--The page-->
<h:commandButton id="createUserSubmit" actionListener="#{createUser.submit_listener}" type="button" value="#Create User" />

<!-- createUser managedBean -->
public void submit_listener(Action ae) {
 System.out.println(ae.getId());
}


當使用者按下「Create User」按鈕(<h:commandButton)時,JSF 的實作會把該事件廣播給任何註冊的 listeners。在這個例子之中,在 createUser 這個 managed bean 的 listener  ——「submit_listener」將會被執行。另一種方式,是使用「actionListener」屬性的辦法,你可以使用 actionListener 標籤。

<h:commandButton id="createUserSubmit" type="button" value="#Create User">
 <f:actionListener type="com.interfrontier.ActionListenerImpl">
</h:commandButton>


這段代碼將會呼叫 ActionListenerImpl 的 processAction 方法。ActionListenerImpl 必須實作 ActionListener 介面,其定義了 processAction 方法:

public void processAction(ActionEvent vce) {
 System.out.println(vce.getId());
}


2. ValueChangeEvent

當某個元件的值改變時,而你希望被通知,例如 text 欄位的文字被修改或 checkbox 被選取時,那 ValueChangeEvent 就會非常有用。大部分的 JSF 元件都支援 valueChangeListener  屬性。讓我們來看一個例子:

<!-- The Page -->
<h:inputText id="name" valueChangeListener="createUser.nameChange_listener" />
<!-- createUser managedBean -->
public void nameChange_listener(ValueChangeEvent vce) {
 System.out.println(vce.getId());
 System.out.println(vce.getOldValue());
 System.out.println(vce.getNewValue());
}

當使用者修改表單中「name」欄位值的時候(<h:inputText>),JSF 實作就會將該事件廣播給任何註冊的 listeners。在這個例子之中, createUser 這個 managed bean 的 listener ——「nameChange_listener」將會被執行。注意 ValueChangeEvent 提供了該元件的原始值以及新(改變後)的值。

另外一種使用「valueChangeListener」屬性的辦法,是使用「valueChangeListener」這個標籤。

<h:inputText id="name">
  <f:valueChangeListener type="com.interfrontier.ValueChangeImpl"/>
</h:inputText>

這段代碼會呼叫 ValueChangeListenerImpl 的 processValueChange 方法。ValueChangeListenerImpl 必須實作 ValueChangeListener 介面,其定義了「processValueChange」方法。

public void processValueChange(ValueChangeEvent vce)
  throws AbortProcessingException {
    System.out.println(vce.getId());
    System.out.println(vce.getOldValue());
    System.out.println(vce.getNewValue());
}

四、結論:

Action Handlers 和 Event Listeners 是 JSF 用來提供事件驅動機制的重要特色。當使用者有任何動作時,例如點擊按鈕或提交表單時,就會發生一個事件。事件通知會透過 HTTP 送給 server,然後被 FacesServlet 處理,並呼叫自訂的業務邏輯或者啟動頁面導覽(page navigation)。

原文出處:http://johnderinger.wordpress.com/2007/10/10/action-handlers-and-event-listeners/

2012年7月1日 星期日

大型EC網站網頁效能調校 ── Image Server

由於筆者本身負責國內某大型 EC 網站的 Infrastructure 規劃,隨著商品圖片與廣告 banners 數量越來越多,網頁與圖片的載入速度也開始逐漸變慢。因此,透過如 FirebugYSlow 以及 WebPageTest 等網頁效能檢測工具去觀察、比較其他同業網站的作法,發現到「Image Server」或許可以解決此一問題,而這也是我撰寫本文的動機。

一、什麼是「Image Server」?

顧名思義,「Image Server」就是專門負責輸出圖檔的 Web Server 或 Reverse Proxy,您可以使用:Apachelighthttpdnginx IIS...等都可以架設這類 Servers。當然,也不是只能放圖檔而已啦,只要是一些網站常用的靜態元件(static components)其實都是可以放在上面的,比方說 HTML、CSS、Javascript、Flash 或影音..等類型的檔案。

如果您還希望能做到兩部以上 servers 的負載平衡或者 HA,那麼您可能還需要在前端架設一組「layer 4 switch」的硬體設備(或稱 Server Load Balancer),不過這類設備通常不便宜,動輒從數十萬到數百萬之間都有。如果您的預算有限,也是可使用「軟體」的方式來架設,例如 HAProxyLVS 就是這方面知名的 Open Source 軟體。

您也可以利用目前很流行的雲端服務來建置這類 server,例如 Amazon 的 EC2。而如果您的網站是全球性或國際性的大網站,則可以考慮請 CDN 業者協助規劃(Akamai)。由於筆者只有實作「layer 4 switch + Apache」的經驗,所以本文僅就此方式來討論。若讀者們有其他的經驗或想法,還請不吝指教。

二、運作原理:

為什麼「Image Server」可以加速網頁與圖片的載入速度?其實要先從「BrowserScope」這個網站開始說起。「BrowserScope」是一個專門蒐集各種瀏覽器版本功能規格的網站,請先連上這個網站:http://www.browserscope.org/、再點擊「Network」頁籤:
圖一、BrowserScope 請點選「Network」這個頁籤
圖一、BrowserScope 請點選「Network」這個頁籤
就可以看到如下的比較表:
圖二、BrowserScope 各種瀏覽器的 Network 功能規格比較表
圖二、BrowserScope 網站提供各種瀏覽器的 Network 功能規格比較表

請注意上圖以紅框標示的「Connections per Hostname」欄位,這就是 Image Servers 之所以能加速圖片或其他靜態元件下載/顯示的原因所在!這個欄位代表「瀏覽器在同一時間內對某一台主機之間最多可以建立幾個 connections」,我們可以看到上表中共列出 23 種瀏覽器版本,而有 16 種瀏覽器在同一時間內對於一組主機名稱(Hostname)最多可以建立 6 個 connections。

這是什麼意思呢?以下藉由「WebPageTest」這個檢測工具來做進一步說明。

 三、使用 WebPageTest 測試國內三大 EC 網站的 Image Server


關於「WebPageTest」的介紹,在此就不贅述了,請自行參考網站說明或對岸朋友的這篇文章:「WebPagetest专业的网站测速工具」。筆者挑選了「Yahoo!奇摩購物中心」、「PChome線上購物」以及「momo富邦購物網」這三家 EC 業者的「首頁」作為此次測試的比較對象。

首先,WebPageTest 的使用方式相當簡單。連上首頁以後,參照下圖以數字標示的順序輸入,最後再按下「SART TEST」即可開始進行檢測,這四個欄位分別是:
  1. 要檢測的網址。
  2. 要從何處進行檢測,因為目前在台灣還沒有人提供 WebPageTest 作測試來源主機,所以我在這裡選擇的是地理位置較近的東京(Tokyo),而東京這一台主機只有「IE 8」一種Browser可以選擇。
  3. 報告的標籤文字,此欄位是 Optional 的,可填可不填,可讓您為您的報告命名。
  4. 開始進行檢測,按下後即可開始進行檢測(或排隊)。
圖三、WebPageTest 設定畫面
約數分鐘以後,檢測結束就會產生檢測報告。由於 WebPageTest 的檢測與評分項目很多,也會提供許多效能上的改善建議,由於本文主題是探討「Image Server」,所以僅就這方面作相關探討,其餘項目就麻煩各位讀者自行研究一下囉。
在 WebPageTest 報告裡面,我們要特別注意的是「Details」裡面的「Connection View」那一張圖表裡頭紫色長條狀的部份(即代表「Image」的部份),如以下三張圖表所示:

2012/07/01 - Yahoo! 購物中心首頁測試結果
圖四、2012/07/01 - Yahoo! 購物中心首頁測試結果

由圖四中,我們可以看到「Yahoo! 購物中心」有三部 Image Server,分別為:「l.yimg.com」、「rp1.monday.vip.tw1.yahoo.net」與「rp2.monday.vip.tw1.yahoo.net」。我們從這張圖可以確認:在同一時間內,WebPageTest 測試主機上的 IE 8 瀏覽器與 Web Servers 之間最多都只能建立 6 個connections,這與圖二 BrowserScope 提供的數據是吻合的。

2012/07/01 - PC home 線上購物 首頁測試結果
圖五、2012/07/01 - PC home 線上購物 首頁測試結果
由圖五中,我們可以看到「PC home 線上購物」則只有一部 Image Server,其主機名稱為「ec1img.pchome.com.tw」,而且在同一時間也是只能建立 6 個 connections。 
2012/07/01- momo 富邦購物網
圖六、2012/07/01- momo 富邦購物網
由圖六中,我們可以看到「momo 富邦購物網」的圖片由五部主機吐出,這大概是因為 「<img src="xxx.jpg" />」的 HTML 標籤未妥善指定到不同 Image Servers 之故,所以導致 www.momoshop.com.tw、img1.momoshop.com.tw img2.momoshop.com.tw、img3.momoshop.com.tw 和  img4.momoshop.com.tw  這五部主機並未妥善發揮使瀏覽器「平行下載」(Parallel Download)的作用,反而讓瀏覽器花了太多時間在「Initial Connection」,卻未 reuse 這些 connections。

另外,嚴格來說,「PC home 線上購物」和「momo 富邦購物網」的 Image Servers 其實都還是有一個小問題。因為根據「Web Performance Best Practices and Rules」一文的「Use Cookie-free Domains for Components」,靜態元件最好與動態網頁分開放置於不同域名,也就是說 Image Server 主機所在的 domain 應該與放置動態頁面的 domain 不一樣,如此 request header 中就不會有許多用不到的 cookie,也減少不必要的頻寬與效能耗損,可參考「Yahoo! 購物中心」的作法,他們就是使用「*.yimg.com」和「*.monday.vip.tw1.yahoo.net」作為 Image Server 域名,以便和動態頁面的「buy.yahoo.com.tw」有所區隔。

靜態元件(圖片)應和動態網頁(程式)分開,使用獨立的 domain
圖七、靜態元件(圖片)應和動態網頁(程式)分開,使用獨立的 domain

四、Image Server 的附帶好處

Image Server 除了能加速圖片或其他靜態元件的下載速度之外,還有以下附帶好處:
  1. 提高消費者的瀏覽與購物意願。
  2. 減少頻寬用量 ─ ─ 透過 cache 以及減少 cookie 區段來減少頻寬的使用。
所以,「Image Server」對於大型 EC 網站絕對是一套不可或缺的系統。