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


          JavaServer Faces(JSF)提供可擴展的組件模型,開發人員可以創建可重用的組件,使用這些自定義組件提高開發效率和降低開發成本。雖然對于定制和重用而言 JSF 的組件模型非常強大,但是開發人員普遍認為開發 JSF 自定義組件并不容易,因為通常至少需要熟悉 JSF encode/decode 和 state holder 的內部機制并覆蓋相應的方法(參考“懷疑論者的 JSF: JSF 組件開發”所述),如 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 個步驟(參考“懷疑論者的 JSF: JSF 組件開發”)。

          1. 擴展 UIComponent

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

            重用技巧:

              • 選擇 UIPanel 作為布局容器,重用標準組件作為復合組件的子組件。
              • 實現內部動作監聽器。

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

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

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

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

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

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

          示例概述

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


          圖 1. 測試頁面中的 Value Scroller
          圖 1. 測試頁面中的 Value Scroller 

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


          圖 2. Value Scroller 的類結構
          圖 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 渲染結果
          ImageHtmlGraphicImageImage顯示圖片
          InputHtmlInputHiddenHidden隱藏類型輸入字段
          HtmlInputSecretSecret密碼類型輸入字段
          UIInput, HtmlInputTextText文本類型輸入字段
          HtmlInputTextareaTextarea多行輸入字段
          MessageUIMessage, HtmlMessageMessage特定組件消息
          MessagesUIMessages, HtmlMessagesMessages所有消息
          OutputHtmlOutputFormatFormat輸出參數化文本
          HtmlOutputLabelLabel輸入字段的文本標簽
          HtmlOutputLinkLink未與命令關聯的鏈接
          UIOutput, HtmlOutputTextText普通文本
          PanelHtmlPanelGridGrid可定制的表格
          HtmlPanelGroupGroup將所包含組件歸為一組
          CheckboxHtmlSelectBooleanCheckboxCheckbox單個復選框
          SelectManyHtmlSelectManyCheckboxCheckbox一組復選框
          UISelectMany, HtmlSelectManyListboxListbox可多選的列表框
          HtmlSelectManyMenuMenu可多選的菜單
          SelectOneHtmlSelectOneRadioRadio單選鈕
          HtmlSelectOneListboxListbox單選列表框
          UISelectOne, HtmlSelectOneMenuMenu單選菜單

          從 表 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.ibm.com/developerworks/cn/java/j-lo-jsf-custom/index.html

          posted on 2011-07-21 10:56 七孑 閱讀(853) 評論(1)  編輯  收藏 所屬分類: Java web技術

          評論

          # re: 編寫 JSF 自定義復合組件的技巧和竅門[未登錄] 2013-08-21 10:04 aaa

          清單 2. 重用標準渲染器創建自定義復合組件
          下面的那段代碼是哪個文件的???  回復  更多評論   

          <2013年8月>
          28293031123
          45678910
          11121314151617
          18192021222324
          25262728293031
          1234567

          導航

          統計

          常用鏈接

          留言簿(1)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 宝清县| 都江堰市| 长沙市| 斗六市| 广汉市| 贵定县| 历史| 奉新县| 镇安县| 西和县| 凉城县| 普洱| 阳原县| 辉南县| 新绛县| 甘泉县| 龙川县| 电白县| 宿松县| 渭源县| 五华县| 介休市| 乐山市| 交城县| 广水市| 浮梁县| 榆林市| 清苑县| 茶陵县| 万州区| 合作市| 如东县| 永顺县| 隆子县| 台中市| 财经| 西城区| 盘锦市| 中阳县| 泽州县| 晋州市|