本文示例源代碼或素材下載
概覽
Wicket 是最近才啟用的 Java Web 開發框架。它是一種開源、輕量、基於組件的框架,這讓 Wicket 迅速從開發 Web 應用程序的常用方法中脫穎而出。Wicket 力圖通過支持基於純 HTML 的模板來清晰地界定 Html 頁面設計人員和 Java 開發人員之間的角色界線,此模板可使用任何的 WYSIWYG Html 設計工具構建,並且經稍許修改就可以具備動態特征。
與其他框架類似,Wicket 也構建在 Sun Microsystems 的 servlet API 之上。不過,與基於 Model-VIEw-Controller (MVC) 模型(比如 Struts)的其他框架不同,Wicket 可以讓您從處理請求/響應對象的任務中解脫出來,而這些任務是諸如 servlet 這類技術所固有的。去掉這些任務後,Wicket 讓您能將精力更多地集中於應用程序的業務邏輯。
作為一個 Wicket 開發人員,應該考慮構建有狀態的可重用組件,而不是構建用來處理請求/響應對象的控制器並且同時還要擔心多線程問題。與構建控制器或動作類相反,您創建的是一個頁面,在這個頁面上放置組件,然後定義每個組件如何響應用戶輸入。
HelloWorld 示例
要真正展示使用 Wicket 開發基於 Web 的應用程序的簡便性,不妨先來開發一個簡單的 “Hello World” 示例。在 Wicket 開發一個動態頁面通常只會涉及創建如下兩個工件:
● Html 模板
● Java 頁面類
注意:必須確保實際的 HTML 文件和頁面類名稱是相同的(例如,HelloWorld.Html 和 HelloWorld.Java)而且二者均處在 CLASSPATH 上的相同位置。而且最好要將二者置於相同的目錄內。
HTML 模板(HelloWorld.Html)
清單 1 中所示的是 HelloWorld 示例的模板文件。
清單 1. HelloWorld.Html
<Html>
<head><script type="text/Javascript" ></script></head>
<body bgcolor="#FFCC00">
<H1 align="center">
<span wicket:id="message">Hello World Using Wicket!</span>
</H1>
</body>
</Html>
要制作動態的Web頁面,需要確定頁面的動態部分並告知Wicket 使用組件來呈現這些部分。在清單1中,我想要獲得動態的消息,於是我使用了span 元素來標記該組件,使用wicket:id 屬性來標示該組件。
Java 頁面類(HelloWorld.Java)
清單 2 所示的是 HelloWorld.Java 示例的頁面類。
清單 2. HelloWorld.Java
package myPackage;
import wicket.markup.Html.WebPage;
import wicket.markup.Html.basic.Label;
public class HelloWorld extends WebPage
{
public HelloWorld()
{
add(new Label("message", "Hello World using Wicket!!"));
}
}
頁面類中 label 組件的 ID("message")必須要與模板文件內此元素的 Wicket ID(wicket:id="message")相匹配。Wicket 的 Java 頁面類包含 Web 頁的所有動態行為。而 Html 模板和頁面類之間是一對一的關系。
最後,需要創建一個 Application 對象,當應用程序由 Web 容器加載時,該對象是起始點,而且它還是進行應用程序初始化設置和配置的地方。比如,可以通過覆蓋 getHomePage() 方法並返回對應於應用程序主頁的頁面類來定義應用程序的主頁,如清單 3 所示。
清單 3. HelloWorldApplication.Java
package myPackage;
import wicket.protocol.http.WebApplication;
public class HelloWorldApplication extends WebApplication {
protected void init() {
}
public Class getHomePage() {
return HelloWorld.class;
}
}
此外,還可以修改或覆蓋默認應用程序設置,具體方法是覆蓋 init() 方法並隨後調用 getXXXSettings() 來檢索可更改的 Settings 對象的一個接口。由這些方法返回的接口如表 1 所示,這些接口可用來配置應用程序的框架設置:
表 1 給出了設置的示例列表,這些設置可應用於 Application 類的應用程序級別。
表 1. 應用程序級設置
方法 使用目的 getApplicationSettings 應用程序的應用程序級別設置 getDebugSettings 應用程序與調試相關的設置 getExceptionSettings 應用程序的異常處理設置 getMarkupSettings 應用程序與標記(Markup)相關的設置 getPageSettings 應用程序與頁面相關的設置 getRequestCycleSettings 應用程序與請求周期相關的設置 getSecuritySettings 應用程序與安全性相關的設置 getSessionSettings 應用程序與會話相關的設置
表 2 給出了一些示例,展示了如何將應用程序級別的設置應用於 Application 類。
表 2. 應用程序級別設置的例子
示例 用途 getApplicationSettings().setPageExpiredErrorPage(<Page class>) 定義在頁面因會話超時而終止時應該顯示的通用頁面 getMarkupSettings().setDefaultMarkupEncoding("UTF-8") 設置 markup 格式以便呈現 getSecuritySettings().setAuthorizationStrategy(<IAuthorizationStrategy Instance>) 設置可用於應用程序的授權策略
web.XML 配置文件
最後,若要加載應用程序並讓其可用,需要定義這個 Wicket servlet 類,在 web.XML 配置文件內用參數的形式為其傳遞應用程序類名,如清單 4 所示。
清單 4. web.XML 配置文件
<?XML version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://Java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>Wicket Hello World Example</display-name>
<servlet>
<servlet-name>HelloWorldApplication</servlet-name>
<servlet-class>
wicket.protocol.http.WicketServlet
</servlet-class>
<init-param>
<param-name>applicationClassName</param-name>
<param-value>myPackage.HelloWorldApplication</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>HelloWorldApplication</servlet-name>
<url-pattern>/hello/*</url-pattern>
</servlet-mapping>
</web-app>
此應用程序現在可以包裝成一個 War/Ear 文件並被部署到任何基於 Java Platform, Enterprise Edition(Java EE)的 servlet 容器,比如 Tomcat 或 WebSphere?,並可通過 URL http://<serverName:port>/warfileName/hello/ 調用,只需用具體的值代替其中的 servername 和 warfilename 即可。在本例中,我調用的是 http://localhost:8090/sample/hello,如圖 1 所示。
圖 1. 示例 HelloWorld Wicket 應用程序
Wicket 生命周期
如能對 Wicket 生命周期有深入的了解將非常有利於更有效地使用 Wicket.這個生命周期包含如下一些步驟:
● 應用程序加載
● 請求處理
● 呈現
應用程序加載
基於 Wicket 的應用程序可通過定義 web.XML 文件內的 Wicket servlet 加載,該文件可載入到任何基於 Java EE 的應用服務器,如清單 5 所示。
清單 5. web.XML
<?XML version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://Java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>Sample Wicket Application</display-name>
<servlet>
<servlet-name>SampleWicketApplication</servlet-name>
<servlet-class> wicket.protocol.http.WicketServlet </servlet-class>
<init-param>
<param-name>applicationClassName</param-name>
<param-value>wicket.sample.SampleApplication</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>SampleApplication</servlet-name>
<url-pattern>/sample/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.Html</welcome-file>
</welcome-file-list>
</web-app>
所定義的 servlet 類必須一直是 wicket.protocol.http.WicketServlet,而 applicationClassName 參數值的類型則必須是 WebApplication .在本例中,SampleApplication 擴展 WebApplication.無論何時,只要客戶機使用 URL /sample/* 調用清單 5 中的應用程序,該服務器就要加載 WicketServlet,而這反過來又能創建應用程序類的一個實例,即 SampleApplication.
所定義的 applicationClassName 參數必須是擴展 WebApplication 的類的完全限定名。如果應用程序類未能找到、不能擴展 WebApplication 或不能被實例化,就會拋出 WicketRuntimeException 類型的運行時異常。
請求處理
應用程序類由 Wicket servlet 加載後,如果沒有會話存在,它就會使用會話庫創建針對 servlet 請求的一個會話。應用程序之後使用這個會話對象創建一個 RequestCycle 對象並委派對 RequestCycle 的調用去處理此請求。
之後,請求周期調用 onBeginRequest 方法以便讓子類進行請求處理之前相應的預處理操作。請求周期會經歷若干狀態並會根據當前狀態向請求周期處理器發出不同的指令。發出所有指令後,此請求周期即達到了其最後的狀態,表明請求處理完畢,如圖 2 所示。
圖 2. Wicket 的請求處理順序流程
請求周期處理器負責處理請求周期內的指令。該處理器會被 RequestCycle 所用以便以預定義的格式調用其方法:
● 調用 IRequestTarget resolve(RequestCycle, RequestParameters) 以獲得請求的目標。例如,請求針對的可能是可被書簽標記的一個頁面,也可能是之前呈現的頁面上的某個組件。這是請求周期處理器的主要功能之一。RequestParameters 對象包含所有可從 servlet 請求參數轉換過來的可選參數並可充當這些參數的強類型的變體(參見 IRequestCodingStrategy)。
● void processEvents(RequestCycle) 的作用是處理類似組件調用這類事件,比如 onClick() 和 onSubmit() 事件處理程序。
● 調用 void respond(RequestCycle) 是為了創建響應,即生成 Web 頁面或是進行重定向。
● 只要在事件處理過程或響應階段發生未被捕獲的異常,void respond(RuntimeException , RequestCycle) 都會被調用以便生成合適的異常響應。
呈現
頁面通過呈現與它相關的標記(頁面的 Html 文件)來呈現頁面自身。正如 MarkupContainer(頁面的超類)會為這個相關的標記遍歷標記流一樣,它會按 ID 搜索與此 標記內的標簽(tag)相關聯的組件。由於 MarkupContainer(在本例中,是一個頁面)已經通過 onBeginRequest() 構造並初始化,每個標簽的子標簽都應該在容器內可用。組件檢索完畢後,會調用它的 render() 方法。
Component.render() 遵循如下步驟來呈現一個組件:
1、決定組件的可見性。如果組件不可見,RequestCycle 的 Response 就會被更改為 NullResponse.getInstance(),它是丟棄輸出的一種響應實現。
2、調用 Component.onRender() 的目的是讓此組件的呈現實現開始呈現此組件。
3、所有組件模型均被分離以減少組件層次結構的大小,並防止它被跨集群復制。
renderComponent 方法獲得此標記流中的下一個 Tag 的可更改版本並調用 onComponentTag(Tag) 以便子類能夠修改此標簽。子類更改了標簽之後,會被 renderComponentTag(Tag) 寫出到此響應,而此標記流則前進到下一個標簽。
接下來,調用 onComponentTagBody(),傳遞已經作為打開標簽寫出的 MarkupStream 和 ComponentTag.這就讓組件可以為了生成此組件標簽的主體而進行所有必要的操作。子類能夠在 onComponentTagBody() 內調用的一個操作是 Component.replaceComponentTagBody(),它用一個任意字符串替代此組件主體內的那個標記。最後,此框架為此組件的打開標簽寫出適當的結束標簽。
創建定制組件
Wicket 的主要特性之一是它提供了開發定制組件的便利性。Wicket 提供了一個非常靈活的組件/編程模型,利用此模型可以輕而易舉地開發定制組件。定制組件可以是 Html 字段的格式,也可以是可在頁面內使用的面板的格式。在基於 Web 的應用程序中,可重用組件的常見格式是頁眉、頁腳、導航欄等。
首先,我們來看看 Wicket 自身的組件層次結構,該結構很容易用來構建一個新的定制組件,也可以對它進行擴展以便添加新特性。
圖 3. 組件的層次結構
組件
Component 位於層次結構的最頂端,充當所有組件的抽象基類。它提供了多種特性,比如
● Identity:必須是非空 ID,並且此 ID 在容器內惟一並能通過 getID() 檢索到
● Model:保存要被作為 Html 響應而呈現的數據
● Attributes:可被添加到任何組件以處理與此組件關聯的標記標簽
WebComponent
WebComponent 充當諸如標簽和圖像這類簡單的 Html 組件的基類。
MarkupContainer
MarkupContainer 保存所有子組件並且自身沒有標記。子組件可通過調用 add() 和 replace() 方法而被添加或替換,並且可以使用 OGNL 注釋進行檢索。例如,調用 get("a.b") 將會返回 ID 為 "b" 且父組件 ID 為 "a" 的組件。
WebMarkupContainer
它充當 HTML 標記和組件的容器。它非常類似於基類 MarkupContainer,只不過標記類型被定義成 Html.
WebMarkupContainerWithAssociatedMarkup
它擴展 WebMarkupContainer 並提供附加特性來處理頁眉標簽。
Panel
Panel 是一個可重用組件,用來保存標記和其他組件。通過擴展此類可以創建可重用的定制組件。
Border
Border 組件具備自身的關聯標記並可被用來定義其關聯標記文件的一部分,以呈現其邊界。
Page
Page 充當所有頁面的抽象基類並可包含 Component 的任意樹。所有生命周期事件,比如 onBeginRequest、onEndRequest() 和 onModelChanged() 都可被 Page 的子類覆蓋,這些子類可處理這些事件。
簡單了解了 Wicket 的組件層次結構後,現在可以來看看如何通過擴展 Wicket 的 Panel 組件構建您自己的定制組件。
借助圖 4,可大致了解一下如何使用 Wicket 編寫示例定制組件(如頁腳)的代碼。
圖 4. Footer 組件的布局
一個具有直觀表示的定制 Wicket 組件通常包含如下工件:
● 一個 HTML 模板(Footer.Html),如清單 6 所示
● 可選的 Javascript、樣式表或圖像相應的 Java Component 類(Footer.Java),這個類擴展標准
● Wicket 發布版自帶的幾個組件基類中的一個,如清單 7 所示。
清單 6. Footer.Html
<wicket:panel>
<hr>
Copyright <span wicket:id="year">2008</span>. IBM Inc. All rights reserved.
</wicket:panel>
清單 7. Footer.Java
import Java.util.GregorianCalendar;
import wicket.markup.Html.basic.Label;
import wicket.markup.Html.panel.Panel;
public final class Footer extends Panel {
public Footer(String id) {
super(id);
add(new Label("year", "" + new GregorianCalendar().get(GregorianCalendar.YEAR)));
}
}
注意:清單 6 和 清單 7 中的工件、Html 工件以及該組件(一個 Java 類)的服務器端的表示通常位於同一個包內。
嵌入/使用組件
要使用組件 — 在定義完之後 — 只需在所需的 Page 類(即 <Page>.java)文件(例如 Home.Java,如清單 8 所示)內對之進行實例化,然後通過將此定制組件的 ID 嵌入到相應的模板(<Template>.html)文件(比如 Home.Html,如清單 9 所示)內對它進行調用。
清單 8. Home.Java
import wicket.markup.Html.WebPage;
public class Home extends WebPage {
public Home() {
add(new Footer("footer"));
}
}
清單 9. Home.Html
<Html>
<head></head>
<body>
Body of Home page.
<span wicket:id="footer">Footer Info</span>
</body>
</Html>
Wicket 驗證
Wicket 支持客戶端和服務器端的表單驗證。驗證是通過內置的驗證器實現的。Wicket 自帶很多驗證器,比如 NumberValidator、StringValidator、PatternValidator、DateValidator 和 RequiredValidator.
此外,還可以編寫一個定制的驗證器來執行那些未內置到 Wicket 內的驗證。對於服務器端驗證而言,在表單提交後,Wicket 就會遍歷置於表單內的所有的表單組件並對組件輸入執行所有的關聯驗證器。在處理過程中,只要驗證器拋出任何錯誤消息,Wicket 都會收集這些錯誤消息。之後,FeedbackPanel 組件顯示所有收集到的錯誤消息。
內置驗證器
Wicket 提供了處理驗證的一種更為簡便的方式。通過 Wicket,當字段組件在頁面文件內創建的時候,就可以在字段組件上設置一個驗證器,比如在頁面內創建一個要求用戶進行輸入的強制字段,如清單 10 所示。
清單 10. 將 TextFIEld 標記為強制輸入字段
TextField firstNameComp = new TextFIEld("firstName");
firstNameComp.setRequired(true);
在 Wicket 內,FeedbackPanel 組件呈現頁面內所有與表單相關的錯誤。實際上,FeedbackPanel 顯示與 Page 內包含的組件相關聯的所有類型的反饋消息。當然,也可以只過濾那些需要顯示的消息類型(信息、錯誤、調試、警告等)。此組件可被添加到頁面,如清單 11 所示。
清單 11. 在頁面類內添加 FeedbackPanel
add(new FeedbackPanel("feedBack"));
對 "feedback" 組件的引用也需要添加到 Html 標記以顯示可能存在的驗證錯誤,如清單 12 所示。
清單 12. 在模板文件內嵌入 FeedbackPanel
<span wicket:id="feedback"></span>
定制驗證器
如果表單級驗證不能通過使用內置驗證器得到處理,就可以創建一個定制驗證器,方法是建立 AbstractFormValidator 類的子類並使用一個構造函數,把要驗證的表單組件作為該函數的參數。定制驗證器需要在表單實例化的時候添加到此表單。
舉個例子,假如需要確保一個表單內兩個給定字段都不為空。由於此目的不能通過使用內置驗證器實現,所以需要編寫一個定制的驗證器,如清單 13 所示。
這個定制驗證器通過擴展 AbstractFormValidator 類創建,如清單 13 所示。
清單 13. EitherInputValidator.Java
import Java.util.Collections;
import wicket.markup.Html.form.Form;
import wicket.markup.Html.form.FormComponent;
import wicket.markup.Html.form.validation.AbstractFormValidator;
import wicket.util.lang.Objects;
public class EitherInputValidator extends AbstractFormValidator {
/** form components to be validated. */
private final FormComponent[] components;
public EitherInputValidator(FormComponent f1, FormComponent f2) {
if (f1 == null) {
throw new IllegalArgumentException(
"FormComponent1 cannot be null");
}
if (f2 == null) {
throw new IllegalArgumentException(
"FormComponent2 cannot be null");
}
components = new FormComponent[] { f1, f2 };
}
public FormComponent[] getDependentFormComponents() {
return components;
}
public void validate(Form form) {
// we have a choice to validate the type converted values or the raw
// input values, we validate the raw input
final FormComponent f1 = components[0];
final FormComponent f2 = components[1];
String f1Value = Objects.stringValue(f1.getInput(), true);
String f2Value = Objects.stringValue(f2.getInput(), true);
if ("".equals(f1Value) || "".equals(f2Value)) {
final String key = resourceKey(components);
f2.error(Collections.singletonList(key), messageModel());
}
}
}
注意:必須調用 FormComponent.getInput 獲得需要驗證的值,而不是獲得相關的模型。這是因為模型是在驗證後才更新的。
要使用定制驗證器,必須將其添加到表單,並為它傳遞需要被驗證的那些字段元素,如清單 14 所示。
清單 14. 在頁面類內使用定制驗證器
Form myForm = new Form("form");
FormComponent f1 = new TextFIEld("firstName");
myForm.add(f1);
FormComponent f2 = new TextFIEld("lastName");
myForm.add(f2);
myForm.add(new EitherInputValidator (f1, f2));
最後,如果驗證失敗,需要將所顯示的消息添加到屬性文件,如清單 15 所示。
清單 15. 屬性文件內的驗證消息
EitherInputValidator = Please enter data for either '${input0}' from ${label0}
or '${input1}' from ${label1}.
聯合使用 AJax 和 Wicket
通過 AJax(Asynchronous Javascript And XML),不僅可以構建富 UI,還能構建高性能的應用程序,因為啟用了 AJax 的應用程序只更新頁面,並不導致整個頁面的刷新。其結果是反饋和用戶體驗十分類似於桌面應用程序。而這要歸功於浏覽器的 XMLHttpRequest 實現,它能讓客戶機與服務器進行異步通信並能基於從服務器收到的響應(使用 Html DOM API)動態處理頁面內容。
Wicket 解決了用戶必須要發送和處理服務器和浏覽器之間的數據的問題。與向客戶機發送數據相反,Wicket 在服務器端呈現組件並發送所呈現的那個標記。僅此可能還不夠,開發 Ajax 行為要更為簡單和迅速。例如,如果想要使用 AJax 更新一個顯示用戶信息的面板,所需做的只是告知 Wicket 此面板應該更新。而根本無需使用 JavaScript 在客戶機上解析由 Wicket 返回的響應,亦無需在客戶機上處理 DOM.
在 Wicket 內,基於 Ajax 的請求被建模為一種行為。Wicket 具有 IBehaviour 接口的幾個實現,比如 AbstractDefaultAjaxBehaviour 和 AjaxFormComponentUpdatingBehaviour,它們進行內部處理並調用 AjaxRequestTarget 內傳遞的 respond() 方法。此目標對象獲取為響應 Ajax 請求而需要發送回浏覽器的實際內容。AJaxRequestTarget 只呈現那些需要添加給它的組件,而內置的 JavaScript 工件則通過初始化 HTML outerHtml 屬性來重新呈現所添加的組件。
讓我們以 AJax AutoCompleteName 為例,基於用戶輸入,應用程序將會預測用戶選擇的幾種可能性。
應用程序由一個具備文本字段元素的頁面(如清單 16 所示)、內存中的一個預定義的名稱(也可以是動態的)列表以及用來在用戶開始輸入文本時顯示可能值列表的一個 AJax 行為組成。
清單 16. HTML 頁面模板(AutoCompleteName.Html)
<Html>
<head>
<wicket:head>
<style>
div.wicket-aa {
font-family: Verdana,"Lucida Grande","Lucida Sans Unicode",Tahoma;
font-size: 12px;
background-color: white;
border-width: 1px;
border-color: #cccccc;
border-style: solid;
padding: 2px;
margin: 1px 0 0 0;
text-align:left;
}
div.wicket-aa ul {
list-style:none; padding: 2px; margin:0;
}
div.wicket-aa ul li.selected {
background-color: #FFFF00; padding: 2px; margin:0;
}
</style>
</wicket:head>
</head>
<body bgcolor="#FFCC00">
<br>
<br>
<form wicket:id="form">
<b>Name :</b> <input type="text" wicket:id="name" size="60" />
</form>
</body>
</Html>
圖 5. Wicket AJax 示例(加載中)
在 Wicket 中,這個自動完成行為是在類 AutoCompleteBehaviour 內建模的。需要擴展此類並為方法 Iterator getChoices(String input); 提供實現,以便基於輸入返回這個可能的用戶列表(清單 17)。
清單 17. 頁面類(AutoCompleteName.Java)
package myPackage;
import Java.util.ArrayList;
import Java.util.Arrays;
import Java.util.Iterator;
import Java.util.List;
import wicket.extensions.AJax.markup.Html.autocomplete.AutoCompleteTextFIEld;
import wicket.markup.Html.WebPage;
import wicket.markup.Html.form.Form;
import wicket.model.Model;
public class AJaxWorld extends WebPage {
private List names = Arrays.asList(new String[] { "Kumarsun", "Ramkishore",
"Kenneth", "Kingston", "Raju", "Rakesh", "Vijay", "Venkat", "Sachin" });
public AJaxWorld() {
Form form = new Form("form");
AutoCompleteTextField txtName = new AutoCompleteTextFIEld("name", new Model()){
protected Iterator getChoices(String input) {
List probables = new ArrayList();
Iterator iter = names.iterator();
while (iter.hasNext()) {
String name = (String) iter.next();
if (name.startsWith(input)) {
probables.add(name);
}
}
return probables.iterator();
}
};
form.add(txtName);
add(form);
}
}
圖 6. Wicket Ajax 示例(AJax 響應)
I18N 支持
Wicket 通過讀取來自特定於本地語言環境(locale)屬性文件的消息提供對 I18N 的支持。而這則是通過在頁面類內使用 StringResourceModel 類或通過在 Html 標記文件內使用 <wicket:message> 標簽實現的。StringResourceModel 也可用來格式化要顯示的本地化了的消息。
I18N 使用 <wicket:message> 標簽
頁面內需要顯示的標簽和其他信息可使用 <wicket:message> 標簽本地化,而不是硬編碼這個標簽,如清單 18 所示。
清單 18. 模板文件(MyPage.Html)
<Html>
<head>
<title></title>
</head>
<body>
<form wicket:id="myForm">
<wicket:message key="first-name">First Name</wicket:message>
</form>
</body>
</Html>
只要 Wicket 遇到 <wicket:message> 標簽,它就會基於在 HTTP 請求對象上設置的本地語言環境從特定於本地語言環境的屬性讀取鍵值。
I18N 使用 StringResourceModel 類
如果不在模板頁內使用 <wicket:message> 標簽,也可以直接在頁面內使用 StringResourceModel 來基於輸入鍵檢索本地化了的消息,如清單 19 所示。StringResourceModel 類的優勢是可以在呈現之前靈活地格式化消息。
清單 19. 頁面類(MyPage.Java)
public class MyPage extends WebPage {
public MyPage() {
Form form = new MyForm("myForm");
String firstNameLabel = new StringResourceModel("first-name").getString();
form.add(new Label("firstName", new Model(firstNameLabel)));
add(form);
}
}
資源包搜索順序
通常,在 Java i18n 系統中,消息由 locale 查找。locale 會自動從 User Agent 字段內的 HTTP 請求頭獲得,並且在 Wicket WebRequest 對象內設置。不過,它也可以用 getSession()。setLocale(Locale.US) 顯式地設置。
如果您需要為某個特定的組件覆蓋這個 locale,可以只覆蓋該組件上的 getLocale(),之後,它將只能由該組件及其子組件使用。如果客戶機沒有提供想要的 Locale,就會使用 Java 代碼默認的 Locale.
Wicket 搜索所有資源文件中名稱與組件層次結構內的組件相同的文件,最後搜索的是應用程序。消息一旦找到,Wicket 就會中止搜索過程。所以,若 Wicket 想要查找一個 MyPanel 內使用的消息,其中 MyPanel 包含在 MyPage 之下的 MyForm 內,而應用程序的名稱為 MyApplication,Wicket 將會這樣查找:
● MyPanel_locale.properties,……,然後是 MyPanel.propertIEs
● MyForm_locale.properties,……,然後是 MyForm.propertIEs
● MyPage_locale.properties,……,然後是 MyPage.propertIEs
● MyApplication_locale.properties,……,然後是 MyApplication.propertIEs (..)
實際上,它還更進了兩步。Wicket 還將為了 MyPanel、MyForm、MyPage 和 MyApplication 的基類而查看屬性文件。若 MyPanel 直接繼承自 Panel、MyForm 直接繼承自 Form、MyPage 直接繼承自 Page、MyApplication 直接繼承自 Application,Wicket 將會查看:
● MyPanel_locale.properties,……,然後是 MyPanel.propertIEs
● Panel_locale.properties,……,然後是 Panel.propertIEs
● MyForm_locale.properties,……,然後是 MyForm.propertIEs
● Form_locale.properties,……,然後是 Form.propertIEs
● MyPage_locale.properties,……,然後是 MyPage.propertIEs
● Page_locale.properties,……,然後是 Page.propertIEs
● MyApplication_locale.properties,……,然後是 MyApplication.propertIEs (..)
● Application_locale.properties,……,最後是 Application.propertIEs (..)
如果所有前面的步驟都失敗,Wicket 將會默認使用 labelId 作為 Label.
注意:如果 MyForm 作為 MyPage 內的內部類建模,那麼 wicket 將會查找名為 MyPage$MyForm.properties 的資源文件。所以,一種最佳的做法是為站點范圍內的消息使用 MyApplication.propertIEs 並在任何其他的屬性文件內覆蓋它們。
圖 7. 資源包搜索順序
對 Wicket 頁面進行單元測試
Wicket 通過使用內置的模仿對象框架提供對容器外單元測試的支持。該框架會確保框架以及應用程序與之交互的環境對象能按配置執行操作,即使是運行於 Java EE servlet 容器之外也應如此。這有助於提高效率,這是因為無需重啟此容器,讓您能集中精力對感興趣的組件進行單元測試。
模仿對象用來單獨測試代碼邏輯的一部分。它們提供了一些方法來讓測試控制這個虛構類的所有業務方法的行為。
Wicket 對單元測試的支持基於的是對 JUnit 框架的擴展。它的 wicket.util.tester.WicketTester 類提供了大量幫助方法,可幫助模仿各種用戶動作(比如單擊鏈接或表單提交)以及行為(比如頁面呈現或聲明錯誤消息的存在)。
清單 20 給出了一個示例生產頁面(MyPage.Java),它具有一些組件和用戶動作,比如頁面提交和鏈接單擊。
清單 20. 頁面類(MyPage.Java)
import wicket.markup.Html.WebPage;
import wicket.markup.Html.basic.Label;
import wicket.markup.Html.form.Button;
import wicket.markup.Html.form.Form;
import wicket.markup.Html.form.TextFIEld;
import wicket.markup.Html.link.Link;
public class MyPage extends WebPage {
public MyPage() {
MyForm form = new Form("myForm");
form.add(new Label("firstNameLabel", "First Name"));
form.add(new Label("lastNameLabel", "Last Name"));
form.add(new TextFIEld("firstName"));
form.add(new TextFIEld("lastName"));
form.add(new Button("Submit"));
form.add(new Link("nextPage") {
public void onClick() {
setResponsePage(new NextPage("Hello!"));
}
});
}
}
測試頁面呈現器的測試用例
需要做的最基本的測試是要確保每個頁面都能正確呈現。該測試的成功執行(如清單 21 所示)能確保模板和頁面層次結構是相匹配的。
清單 21. 頁面呈現器測試(MyPageRenderTest.Java)
import wicket.util.tester.WicketTester;
import junit.framework.TestCase;
public class MyPageRenderTest extends TestCase {
private WicketTester tester;
public void setUp() {
tester = new WicketTester();
}
public void testMyPageBasicRender() {
WicketTester tester = new WicketTester();
tester.startPage(MyPage.class);
tester.assertRenderedPage(MyPage.class);
}
}
測試頁面組件的測試用例
WicketTester 類具備內置的方法來驗證給定的頁面具有所有必需的組件。使用它的 assertComponent() 方法並為其傳遞組件路徑和組件類型,如清單 22 所示。所給定的路徑需要與它所嵌入的頁面相關。
清單 22. 頁面組件測試(MyPageComponentsTest.Java)
import junit.framework.TestCase;
import wicket.markup.Html.form.TextFIEld;
import wicket.util.tester.WicketTester;
public class MyPageComponentsTest extends TestCase {
private WicketTester tester;
public void setUp() {
tester = new WicketTester();
}
public void testMyPageComponents() {
WicketTester tester = new WicketTester();
tester.startPage(MyPage.class);
// assert rendered fIEld components
tester.assertComponent("myForm:firstName", TextFIEld.class);
tester.assertComponent("myForm:lastName", TextFIEld.class);
// assert rendered label components
tester.assertLabel("myForm:firstNameLabel", "First Name");
tester.assertLabel("myForm:lastNameLabel", "Last Name");
}
}
測試 OnClick 用戶動作的測試用例
測試諸如單擊鏈接這樣的用戶動作可以通過使用 WicketTester 的 clickLink() 方法實現,只需為之傳遞鏈接組件 ID 路徑,然後再驗證所呈現的組件即可,如清單 23 所示。
清單 23. 頁面 OnClick 測試
public void testOnClickAction() {
tester.startPage(MyPage.class);
// click link and render
tester.clickLink("nextPage");
tester.assertRenderedPage(NextPage.class);
tester.assertLabel("nextPageMessage", "Hello!");
}
public void testNextPageRender() {
// provide page instance source for WicketTester
tester.startPage(new TestPageSource() {
public Page getTestPage() {
return new NextPage("Hello!");
}
});
tester.assertRenderedPage(YourPage.class);
tester.assertLabel("nextPageMessage", "Hello!");
}
測試表單提交的用戶動作的測試用例
Wicket 內的表單提交可通過使用 Wicket 的 wicket.util.tester.FormTester 類進行測試,該類具有一些 API,可用來設置表單內的字段組件的輸入值,並最終提交此表單。假設提交表單時要顯示的頁面是包含 “Welcome to Wicket” 消息的 Welcome.Java,對表單提交的測試將類似清單 24.
清單 24. 頁面 OnSubmit 動作測試
public void testFormSubmit ()
{
// Create the FormTester object
FormTester ft = tester.newFormTester("myForm");
// Set the input values on the fIEld elements
ft.setValue("firstName", "Kumar");
ft.setValue("lastName", "Nadar");
// Submit the form once the form is completed
ft.submit("Submit");
// Check the rendered page on form submission
tester.asserRenderedPage(Welcome.class);
// verify the message on the rendered page
tester.assertInfoMessage(new String[]{"Welcome to Wicket"});
}
結束語
像Wicket 這樣的以 Plain Old Java Object (POJO) 為中心的框架可被用來以一種無干擾的簡便方式快速構建基於Web 的應用程序。Html 或其他的標記不會受編程代碼的任何干擾和影響,這就讓 UI 設計人員很容易辨別和避免框架標記。
Wicket 讓開發人員能夠以一種類似 Java-Swing 的方式創建頁面,以便避免對 XML 配置文件的過度使用。它還提供了一種十分全面的方式來對所開發的頁面進行單元測試。