Tapestry的rewind一直是學習和使用Tapestry的難點,rewind是用來處理表單提交的,表單默認使用的是DirectService來提交。在詳細介紹之前,先說明下此文中需要用到的一些概念,首先是表單組件,我這里指的是指繼承自AbstractFormComponent類的組件,例如:TextField、TextArea、Checkbox等,而不是具體的Form組件,表單組件使用時必須在Form組件中,這些組件在rewind時調用繼承自AbstractFormComponent的rewindFormComponent來讀取數據,并將數據賦值給容器或者頁面。
我們來看一下最簡單的TextField組件,組件定義如下
- <input jwcid="price@TextField" type="text" value="ognl:picture.price" translator="translator:number,pattern=##.##" validators="validators:min=0" displayName="價格" class="input_text"/>
<input jwcid="price@TextField" type="text" value="ognl:picture.price" translator="translator:number,pattern=##.##" validators="validators:min=0" displayName="價格" class="input_text"/>
再看一下TextField中的rewindFormComponent組件方法
- protected void rewindFormComponent(IMarkupWriter writer, IRequestCycle cycle) {
-
- String value = cycle.getParameter(getName());
-
- try {
-
- Object object = getTranslatedFieldSupport().parse(this, value);
-
- getValidatableFieldSupport().validate(this, writer, cycle, object);
-
- setValue(object);
- } catch (ValidatorException e) {
- getForm().getDelegate().record(e);
- }
- }
protected void rewindFormComponent(IMarkupWriter writer, IRequestCycle cycle) {
//從請求中得到參數值
String value = cycle.getParameter(getName());
try {
//用translator來轉換值
Object object = getTranslatedFieldSupport().parse(this, value);
//用validators來驗證值
getValidatableFieldSupport().validate(this, writer, cycle, object);
//賦值給容器或者頁面
setValue(object);
} catch (ValidatorException e) {
getForm().getDelegate().record(e);
}
}
可以看到在rewindFormComponent中,主要是從請求中取得用戶輸入的值,然后進行處理,最后賦值給容器或者頁面,上面的例子中會調用頁面類的getPicture().setPrice(“用戶輸入的值”)來進行賦值。這樣整個表單的提交就可以理解為所有的表單組件讀取用戶輸入的值并賦值給頁面的過程。
整個表單提交的詳細處理過程如下:
* initialize():頁面初始化
* pageBeginRender() ("rewind"):getRequestCycle().isRewinding()為true
* rewind of the form / setting of properties:所有表單組件調用rewindFormComponent來取值賦值
* Deferred listeners (for Submit components):調用Submit組件的listener
* Form's listener:調用Form組件的listener
* pageEndRender() ("rewind"): getRequestCycle().isRewinding()為true
* pageBeginRender() (normal): getRequestCycle().isRewinding()為false
* pageEndRender() (normal): getRequestCycle().isRewinding()為false
我們可以看到pageBeginRender和pageEndRender被調用了兩次,兩次中的區別為RequestCycle().
isRewinding,因為我們在使用時經常利用pageBeginRender的初始化值,所以這里有很多使用上的誤區,如果在pageBeginRender中從數據庫讀取數據來初始化跟表單提交無關的變量的話,就可能被調用兩次,這個是應該避免的。什么叫跟表單提交無關的變量呢,就是表單組件中跟賦值無關的,例如上邊提到的value="ognl:picture.price",這時picture就是與表單提交相關的變量,如果你沒有初始化,那么在賦值時調用getPicture().setPrice()就會出現空指針異常,因為這是的picture為null。我們舉個例子來看一下表單無關的變量,假如這個picture頁面會顯示一個創建picture的表單和所有picture的列表,那這個picture的列表就是與表單提交無關的變量,如果你在pageBeginRender中初始化的話,就需要區分是否rewind,否則表單提交時就會被初始化兩次,讓我們看一下代碼:
- public abstract void setPictures(List<Picture> pictures);
- public abstract void setPictureInList();
- public abstract void setPicture(Picture picture);
- public abstract Picture getPicture();
- public void pageBeginRender(PageEvent event) {
- if(getPicture()==null){
- setPicture(new Picture());
- }
- setPictures(getPictureService().findAll());
- }
public abstract void setPictures(List<Picture> pictures);
public abstract void setPictureInList();//用于For中的value
public abstract void setPicture(Picture picture);//用于表單創建
public abstract Picture getPicture();
public void pageBeginRender(PageEvent event) {
if(getPicture()==null){
setPicture(new Picture());
}
setPictures(getPictureService().findAll());
}
判斷picture是否為null并賦值在頁面顯示和rewind中都是需要的,因為頁面顯示時,需要調用getPicture().getPrice(),頁面rewind時,需要調用getPicture().setPrice(),這兩個階段中的picture都不能為null。但setPictures會在表單提交時被調用兩次,在rewind階段初始化它是沒有用處的,所以這時就要對是否rewind進行判斷。修改后的代碼如下:
- public void pageBeginRender(PageEvent event) {
- if(getPicture()==null){
- setPicture(new Picture());
- }
- if (!event.getRequestCycle().isRewinding()) {
- setPictures(getPictureService().findAll());
- }
- }
public void pageBeginRender(PageEvent event) {
if(getPicture()==null){
setPicture(new Picture());
}
if (!event.getRequestCycle().isRewinding()) {
setPictures(getPictureService().findAll());
}
}
這樣就可以避免在rewind時對pictures進行不必要的賦值。這里還要提到的一點是頁面顯示和提交后的頁面很可能不是同一個頁面類的實例,大家都知道頁面類的實例是從實例池取到的,用戶打開頁面顯示表單完后的頁面類實例和用戶提交表單時的用來rewind的頁面類實例不一定是同一個,即使是一個實例,也是被重新初始化過的,不要想當然的認為顯示表單后再提交那個實例應該保存原來顯示的東西,這個應該理清楚。
原文地址:http://www.javaeye.com/article/41724