程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 編寫JSF自定義復合組件的技巧和竅門

編寫JSF自定義復合組件的技巧和竅門

編輯:關於JAVA

本文介紹開發 JavaServer Faces(JSF)自定義復合組件的新思路,提供如何快速開發可重用 JSF 組 件的技巧,而不用像傳統方式那樣自己實現渲染器 (renderer)、狀態管理和事件監聽器。本文提供的 原則和技術也對一般的 JSF 開發很有幫助。

介紹

JavaServer Faces(JSF)提供可擴展的組件模型,開發人員可以創建可重用的組件,使用這些自定義 組件提高開發效率和降低開發成本。雖然對於定制和重用而言 JSF 的組件模型非常強大,但是開發人員 普遍認為開發 JSF 自定義組件並不容易,因為通常至少需要熟悉 JSF encode/decode 和 state holder 的內部機制並覆蓋相應的方法,如 encodeBegine()、decode()、saveState() 和 restoreState( ) 等,對於開發復雜的自定義組件,甚至需要深入理解更多的接口,如 NamingContainer、StateHolder 、EditableValueHolder 和 ActionSource 等接口。

然而,重用 JSF 標准組件的功能可以極大地簡化自定義組件的開發,尤其對於自定義復合組件更是如 此。在大部分情況下,我們可以重用 JSF 框架已經提供的標准渲染器、狀態管理、事件監聽器、轉換器 和驗證器。已有的文章或書籍對如何重用這些標准功能涉及很少,本文基於重用的策略提出快速開發 JSF 自定義復合組件的原則和技巧。

本文首先總結了 JSF 組件開發的通用原則,然後通過一個例子(Value Scroller 自定義復合組件) 的開發詳解說明了哪些標准功能可以重用以及如何重用,以達到簡化 JSF 自定義復合組件開發的目的。

原則與技巧

開發 JSF 自定義復合組件主要有兩個原則,一方面強調重用已有的標准組件;另一方面如何確保自定 義組件易於重用。

1、盡可能的重用標准組件的功能和實現

傳統的自定義復合組件開發建議完全覆蓋實現 encode/decode 邏輯,但這樣做耗費時間而且容易出錯 。毫無疑問,我們可以通過重用標准組件的渲染器等機制減少甚至根本不用自行編寫這部分代碼。另外, 為了實現靈活的配置和使用,自定義復合組件通常需要提供很多屬性,我們需要寫很多代碼來處理這些屬 性的讀寫和狀態管理。實際上,我們可以簡單地把自定義復合組件的屬性傳遞給它自身包含的標准組件, 由已有的標准代碼去處理這些屬性,而不用重復寫這些代碼。

2、清晰地分離組件類、標簽類和模型類

JSF 的組件模型建議在組件類、標簽類和模型類之間有明確清晰的責任分配,以便於重用和擴展。組 件類不應該依賴於 javax.faces.component.html 包,因為組件類不僅可以用於 HTML,而且還應該可以 重用於其它標記語言(如 WML)。也就是說組件類不應該直接引用 javax.faces.component.html 包內的 HTML 組件。例如,在你的組件類中創建一個 HtmlCommandButton 的實例是不可取的,你應該考慮用 javax.faces.component 包中的 UICommand .另一方面,如果你希望你的模型類可以重用於不同的 Web 框架,那麼你的模型類就不應該依賴於 JSF 的任何包,即模型類只表示業務對象而不包含任何用戶界面 相關的組件、數據和狀態。

基於這些原則,對比傳統方式和本文介紹的技巧,我們可以發現基於重用的開發策略會極大簡化 JSF 自定義復合組件的編寫。開發 JSF 自定義組件通常需要如下 3 個步驟。

1、擴展 UIComponent

傳統方式:創建一個類,擴展 UIComponent,保存組件狀態,在 faces-config.xml 中注冊組件

重用技巧:

■ 選擇 UIPanel 作為布局容器,重用標准組件作為復合組件的子組件。

■ 實現內部動作監聽器。

2、定義渲染器或者內聯實現它

傳統方式:覆蓋實現 encode/decode,在 faces-config.xml 中注冊渲染器。

重用技巧:重用標准渲染器類型。

3、創建自定義標簽,繼承 UIComponentTag

傳統方式:返回渲染器類型和組件類型,設置 JSF 表達式屬性

重用技巧:傳遞屬性值給作為子組件的標准組件。

示例概述

我們通過一個自定義復合組件 Value Scroller 的開發步驟說明如何運用多種技巧重用標准組件的功 能和實現,達到簡化開發易於重用的目的。 Value Scroller 可以讓你通過點擊增值或減值按鈕來輸入數 值,而不用手工鍵入,如 圖 1 所示。這個示例只包含最基本的功能,如只支持整型數值輸入,但對於本 文要介紹的內容已經足夠了。

圖 1. 測試頁面中的 Value Scroller

測試頁面中的 Value Scroller" src="http://java.chinaitlab.com/UploadFiles_8734/200905/20090527111440586.png" width=382 twffan="done">

圖 2 說明了 Value Scroller 的基本類結構,遵循 MVC 模式。組件類 ValueScroller 擴展了 UIPanel,作為控制器(Controller)負責與用戶的交互。標簽類 ValueScrollerTag 繼承了 UIComponentTag, 作為視圖(View)處理頁面顯示。與 Value Scroller 綁定的值對象作為模型(Model )存儲用戶鍵入的數值。

圖 2. Value Scroller 的類結構

在後面章節中,本文將結合 Value Scroller 示例說明如何應用前面提到的原則和技巧快速開發 JSF 自定義復合組件。

選擇 UIPanel 作為容器

創建 JSF 自定義復合組件的第一步就是要選擇一個標准組件類進行擴展。通常我們會考慮將這個組件 類作為容器,在其中嵌入子組件,從而構成復合組件。這裡選擇繼承 UIPanel 作為 Value Scroller 的 容器,以 Grid 的方式渲染生成頁面,並且其中包含一個 UIInput 和兩個 UICommand,分別作為數值輸 入框和加減值按鈕,如 清單 1 所示:

清單 1. 擴展類 UIPanel

 public class ValueScroller extends UIPanel { 

    /** 

    * The default constructor 

    * 

    */ 

    public ValueScroller() { 

        super(); 

        addChildrenAndFaces(); 

    } 

}

作為 Value Scroller 子組件的那些標准組件將在 addChildrenAndFaces 方法中加入布局容器之中。

重用標准渲染器類型

接著,我們開始創建 Value Scroller 的子組件,並且實現渲染器的功能。按照傳統方式,必須覆蓋 UIComponent 的 encodeBegin() 和 decode() 方法,但是,如果我們開發的復合組件只是由多個標 准組件構成,我們完全可以將不依賴於特定標記語言的標准組件基類加入到自定義組件中,並且為每個標 准組件設定一個標准的渲染器類型,就可以完成復合組件要實現的渲染器功能。重用標准組件渲染器類型 好處在於兩方面:減少開發的工作量和可能出錯的機會,對於 JSF 初學者尤為重要;不用實現與特定標 記語言相關的 encode/decode 邏輯,使組件類更易於重用。

“JavaServer Faces 實戰” 這本書列出了 JSF 規范提供的標准渲染器類型。

表 1. JSF 標准渲染器

控件族 組件類 渲染器類型 HTML 渲染結果 Image HtmlGraphicImage Image 顯示圖片 Input HtmlInputHidden Hidden 隱藏類型輸入字段 HtmlInputSecret Secret 密碼類型輸入字段 UIInput, HtmlInputText Text 文本類型輸入字段 HtmlInputTextarea Textarea 多行輸入字段 Message UIMessage, HtmlMessage Message 特定組件消息 Messages UIMessages, HtmlMessages Messages 所有消息 Output HtmlOutputFormat Format 輸出參數化文本 HtmlOutputLabel Label 輸入字段的文本標簽 HtmlOutputLink Link 未與命令關聯的鏈接 UIOutput, HtmlOutputText Text 普通文本 Panel HtmlPanelGrid Grid 可定制的表格 HtmlPanelGroup Group 將所包含組件歸為一組 Checkbox HtmlSelectBooleanCheckbox Checkbox 單個復選框 SelectMany HtmlSelectManyCheckbox Checkbox 一組復選框   UISelectMany, HtmlSelectManyListbox Listbox 可多選的列表框   HtmlSelectManyMenu Menu 可多選的菜單 SelectOne HtmlSelectOneRadio Radio 單選鈕   HtmlSelectOneListbox Listbox 單選列表框   UISelectOne, HtmlSelectOneMenu Menu 單選菜單

從 表 1 可以看出,一個組件基類通常對應於多個渲染器類型(如果使用 HTML 作為標記語言,即對 應於多個 HTML 元素),因為組件基類只定義了通用的數據和行為。比如說,UICommand 有兩個 HTML 子 類 HtmlCommandButton 和 HtmlCommandLink,分別對應於渲染器類型 javax.faces.Link 和 javax.faces.Button .當我們想在一個復合組件內部包含一個鏈接時,只需要創建一個 UICommand 實例 ,並將其渲染器類型設置為 javax.faces.Link,而不用從頭覆蓋實現 encodeBegin() 和 decode() 方法。清單 2 列出了 Value Scroller 中的子組件如何在組件類 ValueScroller 中被創建,以及渲染器 等屬性如何被設定。

清單 2. 重用標准渲染器創建自定義復合組件

 private static final String PANEL_GRID_RENDERER = "javax.faces.Grid"; 

private static final String INPUT_TEXT_RENDERER = "javax.faces.Text"; 

private static final String COMMAND_LINK_RENDERER = "javax.faces.Link"; 

private static final String GRAPHIC_IMAGE_RENDERER = "javax.faces.Image";

/** 

* Add children to the base container 

* 

*/ 

private void addChildrenAndFaces() { 

// Set attributes of the base container 

this.setRendererType(PANEL_GRID_RENDERER); 

this.getAttributes().put(COLUMNS_ATTRIBUTE, new Integer(2)); 

// Add the input component 

input = new UIInput(); 

input.setId(INPUT_ID); 

input.setRendererType(INPUT_TEXT_RENDERER); 

this.getChildren().add(input); 

// Add the container of the up and down links 

UIPanel linkContainer = new UIPanel(); 

linkContainer.setId(LINKPANEL_ID); 

linkContainer.setRendererType(PANEL_GRID_RENDERER); 

linkContainer.getAttributes().put(COLUMNS_ATTRIBUTE, new Integer(1)); 

ScrollerActionListener listener = new ScrollerActionListener(); 

// Add the up link 

UICommand upLink = new UICommand(); 

upLink.setId(UPLINK_ID); 

upLink.setRendererType(COMMAND_LINK_RENDERER); 

upLink.addActionListener(listener); 

UIGraphic upImage = new UIGraphic(); 

upImage.setId(UPIMAGE_ID); 

upImage.setRendererType(GRAPHIC_IMAGE_RENDERER); 

upImage.setUrl(UPIMAGE_URL); 

upLink.getChildren().add(upImage); 

linkContainer.getChildren().add(upLink); 

// Add the down link 

UICommand downLink = new UICommand(); 

downLink.setId(DOWNLINK_ID); 

downLink.setRendererType(COMMAND_LINK_RENDERER); 

downLink.addActionListener(listener); 

UIGraphic downImage = new UIGraphic(); 

downImage.setId(DOWNIMAGE_ID); 

downImage.setRendererType(GRAPHIC_IMAGE_RENDERER); 

downImage.setUrl(DOWNIMAGE_URL); 

downLink.getChildren().add(downImage); 

linkContainer.getChildren().add(downLink); 

this.getChildren().add(linkContainer); 

}

將屬性值傳遞給標准組件

我們先看一下標簽描述文件(TLD)中定義的 Value Scroller 提供的屬性。

清單 3. 在 TLD 中定義自定義復合組件的屬性

 <tag> 

  <name>valueScroller</name> 

  <tag-class>component.taglib.ValueScrollerTag</tag-class> 

  <body-content>JSP</body-content> 

  <attribute> 

    <name>id</name> 

    <description>ValueScroller ID</description> 

  </attribute> 

  <attribute> 

    <name>value</name> 

    <description>ValueScroller value</description> 

  </attribute> 

  <attribute> 

    <name>size</name> 

    <description>Input field size</description> 

  </attribute> 

  <attribute> 

    <name>min</name> 

    <description>Minimum value</description> 

  </attribute> 

  <attribute> 

    <name>max</name> 

    <description>Maximum value</description> 

  </attribute> 

  <attribute> 

    <name>step</name> 

    <description>Scrolling step</description> 

  </attribute> 

</tag>

我們看到,除了 min/max/step 是自定義的屬性之外,其他的都屬於 JSF 標准組件的屬性,可以直接 傳遞給構成 Value Scroller 的標准組件處理,完全不用為這些標准組件的屬性覆蓋實現方法 saveState () 和 restoreState() .

通常有兩種方法傳遞屬性值。當你需要對屬性進行一些額外的操作(如驗證或者轉換等),可以在標 簽類 ValueScrollerTag 中將屬性傳遞給自定義組件類,如下所示:

清單 4. 傳遞自定義屬性

 /** 

* Override the setProperties method 

*/ 

protected void setProperties(UIComponent component) { 

    super.setProperties(component); 

    ValueScroller vs = (ValueScroller)component; 

    Application app = FacesContext.getCurrentInstance().getApplication(); 

    // Set value attribute 

    if (value != null) { 

        if (isValueReference((String)value)) { 

            ValueBinding vb = app.createValueBinding((String)value); 

            vs.setValueBinding("value", vb); 

        } else { 

            throw new IllegalArgumentException("The value property must be a 

value " + 

                "binding expression that points to a bean property."); 

        } 

    } 

    // Set id attribute 

    if (id != null) { 

        vs.setId((String)id); 

    } 
    // Set other attributes 

    vs.setMin(min); 

    vs.setMax(max); 

    vs.setStep(step);

}

另外一種方法就是在標簽類 ValueScrollerTag 中直接把屬性值加入相應標准組件的屬性 Map 。例如 ,將 size 屬性傳遞給自定義復合組件包含的 UIInput:

清單 5. 傳遞標准屬性

vs.findComponent("input").getAttributes().put ("size", new Integer(size));

實現內部動作監聽器

在 Value Scroller 中,點擊增值或減值按鈕,輸入框內的值會隨之增大或者減小。我們可以簡單地 在組件類 ValueScroller 中實現一個內部動作監聽器,重用 UICommand 的事件處理邏輯。

清單 6. 實現 Value Scroller 動作監聽器

/**
 * Internal action listener for Value Scroller
 * _cnnew1@author cll
 *
 */
 private class ScrollerActionListener implements ActionListener {
    public void processAction(ActionEvent e) {
        // Only Integer is supported for this demo
        if (input.getValue() instanceof Integer) {
            String commandId = ((UICommand)e.getSource()).getId();
            int value = ((Integer)input.getValue()).intValue();
            // Increase value if the up link is clicked
            if (commandId.equals(UPLINK_ID)) {
                if (value + getStep() > max) {
                    input.setValue(new Integer(max));
                } else {
                    input.setValue(new Integer(value + getStep()));
                }
            }
            // Decrease value if the down link is clicked
            else if (commandId.equals(DOWNLINK_ID)) {
                if (value - getStep() < min) {
                    input.setValue(new Integer(min));
                } else {
                    input.setValue(new Integer(value - getStep()));
                }
            }
        } else {
            throw new IllegalArgumentException(
	      "Unsupported binding type, " +
	      "and only Integer instance allowed for this demo.");
        }
    }
 }

最後,在調用 addChildrenAndFaces 方法創建添加子組件的時候,將這個自定義動作監聽器添加到增 值和減值組件中去。

清單 7. 注冊 Value Scroller 動作監聽器

ScrollerActionListener listener = new ScrollerActionListener();
upLink.addActionListener(listener);
downLink.addActionListener(listener);

使用 Value Scroller

Value Scroller 的開發已經完成,使用也非常簡單,首先在 faces-config.xml 中聲明引用 Value Scroller,如下所示:

清單 8. 在 faces-config.xml 中聲明引用 Value Scroller

<component>
<component-type>xyz.ValueScroller</component-type>
<component-class>
component.ValueScroller
</component-class>
</component>

然後,在測試 JSP 頁面 Test.jsp 上包含 Value Scroller 的標簽描述文件。

清單 9. 在 JSP 中包含 Value Scroller 的 TLD

<%@ taglib uri="/WEB- INF/lib/ValueScroller.tld" prefix="xyz"%>

最後,在 Test.jsp 頁面上使用 Value Scroller 的標簽,並且指定 size/min/max/step 屬性值,部 署運行,就可以看到 圖 1 所示的結果了。

清單 10. 在 JSP 中創建 Value Scroller 並設置屬性

<xyz:valueScroller value="#{pc_Test.itemCount}" size="5" min="-50" max="10000" step="2">
</xyz:valueScroller>

結束語

我們可以看到,本文介紹的自定義復合組件 Value Scroller 的實現沒有編寫 encode/decode 和 state/event 管理相關的邏輯,簡單、快速、並且易於重用。本文總結的 JSF 自定義復合組件的開發技 巧在很大程度上降低了復雜度和工作量,優於傳統的開發方式。

本文配套源碼:http://www.bianceng.net/java/201212/823.htm

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved