http://dev2dev.bea.com.cn/techdoc/wlportal/20031029.html基本頁面流框架能夠為Web項目的一組頁面集中管理導航狀態和邏輯。盡管這比以往的Web開發模式有了明顯的進步,但隨著導航邏輯遍布各個頁面、狀態被存儲在眾多會話對象當中,某些更高級的頁面流特性可以使您的項目更加出色和強大。
本文假定讀者熟悉如何在WebLogic Workshop中構建和運行頁面流。文中將會介紹三個特性:嵌套、聲明性異常處理、Global.app。
嵌套
默認情況下,在一個新頁面流中執行動作會導致當前頁面流失效,這使得您可以為項目的不同部分創建獨立的控制器,并最小化了每次用戶會話需要保存的數據量。每個頁面流管理它自己的狀態和邏輯。在WebLogic Workshop IDE中,將控制權交給另外一個頁面流用一個代表外部頁面流的暗淡的終端節點表示。
想要更深入地了解高級頁面流?請下載 本文附帶的作者示例應用。
頁面流嵌套使開發人員可以更好地將項目分解成獨立的、自包含的功能塊。它的核心功能是把當前頁面流暫時放在一邊,并將控制權交給另外一個頁面流,該頁面流將來會返回到原來的頁面流。
什么時候應該使用頁面流嵌套呢?當執行以下任務時嵌套十分有用:
- 從用戶那里搜集用于當前頁面流的數據
- 允許用戶改正錯誤,或在執行特定動作的過程中提供額外信息
- 為當前頁面流中的數據提供另外一種顯示視圖
- 顯示在當前頁面流中有用的用戶信息(例如,幫助窗口)
讓我們從一個簡單的例子開始。在這個場景中,用戶被要求選擇一種顏色并根據選定的顏色被重定向到不同的頁面。如果沒有嵌套,頁面流(/noNesting/noNestingController.jpf)可能是這樣的:
如果顏色選擇的部分更為復雜(比如在繼續之前需要一個確認過程),就可以采用一個嵌套頁面流來替代chooseColor.jsp,它同樣可以滿足要求(位于/simpleNesting/simpleNestingController.jpf):
嵌套頁面流的一個重要特性是它可以替代頁面的許多工作。嵌套頁面流可以啟動動作,甚至可以進行“post”,或者用表單進行初始化。通常,總是可以用嵌套頁面流來替代頁面。以下是該嵌套頁面流的流程視圖/chooseColor/chooseColorController.jpf:
該頁面流允許用戶選擇一種顏色,請求確認,然后在原來的頁面流上啟動一個動作。該頁面流出于激活狀態時,原來的頁面流被保存在用戶會話中(位于堆棧中)。當該頁面流結束時,被保存的原頁面流上的“chooseRed”或“chooseBlue”動作將被激活。
創建和使用嵌套頁面流是相當簡單的。頁面流向導提供了一個“make this a nested page flow”選項,它可以用類一級的注解@jpf:controller將新頁面流定義為嵌套頁面流:
/**
* @jpf:controller nested="true"
*/
public class chooseColorController extends PageFlowController
|
想讓嵌套頁面流在原頁面流上啟動一個動作,您可以定向到一個“退出節點”(頁面流面板上的紅色方塊“Exit”),它的動作方法形如下面所示:
/**
* @jpf:action
* @jpf:forward name="red" return-action="chooseRed"
* @jpf:forward name="blue" return-action="chooseBlue"
*/
public Forward done(ChooseColorForm form)
{
if ( form.getChosenColor().equals( "Red" ) )
{
return new Forward( "red" );
}
else
{
return new Forward( "blue" );
}
}
|
定向到@jpf:forward ——它定義了一個return-action而不是path——時,嵌套頁面流就會退出。
下面介紹一個稍微復雜的例子。在/demoNesting/demoNestingController.jpf中,用戶進入一個嵌套頁面流(/chooseAirport/chooseAirportController.jpf),該頁面流是幫助用戶查找機場的向導。嵌套頁面流將選定的機場返回(或者說“post”)到原頁面流中,原頁面流繼續執行。
該頁面流展示了與嵌套相關的兩個新特性:
·如果嵌套頁面流激活一個“chooseAirportCancelled”動作,頁面流將會返回到剛才展示給用戶的前一頁面。這是通過在@jpf:forward中使用return-to屬性達到的:
/*
* @jpf:action
* @jpf:forward name="previousPage" return-to="page"
*/
protected Forward chooseAirportCancelled()
{
return new Forward( "previousPage" );
} |
·當啟動“chooseAirportDone”動作時,嵌套頁面流會返回一個表單(FormData 類)。這些將在后面討論,但值得注意的是:頁面流處理該返回表單的方式與它處理頁面post過來的表單的方式一樣。
嵌套頁面流如下,/chooseAirport/chooseAirportController.jpf:
唯一不同的地方在于:在原頁面流中啟動“chooseAirportDone”動作時將會同時返回一個表單。這是通過在@jpf:forward中使用return-form 屬性實現的:
/*
* @jpf:action
* @jpf:forward name="done"
* return-action="chooseAirportDone"
* return-form="_currentResults"
*/
protected Forward confirmResults()
{
return new Forward( "done" );
}
|
在本例中,_currentResults 是嵌套頁面流的一個成員變量。或者,如果您想要在本地初始化一個表單(避免使用成員變量),可以用return-form-type 屬性聲明表單類型,并將表單加入到返回的Forward 對象上:
/*
* @jpf:action
* @jpf:forward name="done"
* return-action="chooseAirportDone"
* return-form-type="ChooseAirportForm"
*/
protected Forward confirmResults( ConfirmationForm confirmForm )
{
ChooseAirportForm returnForm = new ChooseAirportForm( ?);
return new Forward( "done", returnForm );
} |
除了@jpf:controller nested="true" 聲明、激活退出動作和返回表單的模式之外,聲明嵌套頁面流與聲明非嵌套頁面流一樣。
其它注意事項
- 定向/重定向到一個嵌套頁面流或在其上啟動任何動作都會引發嵌套。原頁面流被保存到“嵌套堆棧”中,直到嵌套頁面流在@jpf:forward中使用return-action 屬性來啟動一個返回動作。
- 頁面流可以自嵌套
- 在嵌套頁面流的動作方法中,您可以通過調用PageFlowUtils.getNestingPageFlow( getRequest() ) 來獲得原頁面流的一個引用;
- 嵌套頁面流(任何其它頁面流也是這樣)可以在begin動作方法中接收一個FormData類型的參數。
/**
* @jpf:action
* @jpf:forward name="firstPage" path="index.jsp"
*/
protected Forward begin( InitForm form )
{
?
}
|
為了初始化嵌套頁面流,原頁面流可能會像下面這樣定向到嵌套頁面流:
/**
* @jpf:action
* @jpf:forward name="nest" path="/nested/nestedController.jpf"
*/
protected Forward goNested()
{
nested.nestedController.InitForm initForm
= new nested.nestedController.InitForm();
initForm.setValue( ?);
return new Forward( "nest", initForm );
}
|
- 嵌套頁面流實際定義將要返回的表單(FormData 類)。這和頁面的行為不同,后者只是簡單地將數據作為request參數post過去,這些參數被添加(或誘騙到)到與將要啟動的動作相關聯的表單bean中。更為精確的嵌套頁面流return-form行為可以嵌套兩個啟動同一動作的頁面流。處理來自兩個嵌套頁面的動作類似下面這樣:
/**
* @jpf:action
* @jpf:forward ?
*/
public Forward done( nested1.nested1Controller.OutputForm form )
{
?
}
/**
* @jpf:action
* @jpf:forward ?
*/
public Forward done( nested2.nested2Controller.DoneForm form )
{
?
}
|
異常處理
盡管可以在動作方法中通過編程來處理異常,但為了僅通過聲明就可以處理異常,頁面流還提供了一種更強大的框架。在動作方法和類級別上,處理異常的方式可以是直接定向到一個頁面(或一個嵌套頁面流),也可以是調用一個異常處理方法,由該方法決定下一步顯示的頁面。
直接定向到一個URI是處理異常最簡單的方法:
/**
* @jpf:catch type="Exception" path="/error.jsp"
* @jpf:catch type="NotLoggedInException"
* path="/login/loginController.jpf"
*/
public class exampleController extends PageFlowController |
在上面的例子中,當一個NotLoggedInException 異常被拋出時,用戶將看到/login/loginController.jsp。當其它異常被拋出時,用戶看到/error.jsp (注意:頁面流異常處理總是試圖先匹配那些最具體的異常)。異常自動被存儲在request的 JSP 標簽中,該標簽可以顯示異常信息和/或堆棧跟蹤記錄。
也可以用異常處理方法來處理異常。為此,請在@jpf:catch中使用method屬性:
* @jpf:catch type="ExampleException"
* method="handleExampleException"
* message-key="myCustomMessage"
|
method屬性指向一個異常處理方法,message-key 屬性用來解析資源包中的某條信息,該資源包是通過@jpf:message-resources 標簽(該標簽負責在/WEB-INF/classes/exceptions/Messages.properties中查找資源)在類級別上聲明的:
* @jpf:message-resources resources="exceptions.Messages" |
異常處理方法本身被定義成下面這樣:
/**
* @jpf:exception-handler
* @jpf:forward name="errorDetailsPage" path="errorDetails.jsp"
*/
protected Forward handleExampleException( ExampleException ex,
String actionName,
String message,
FormData form )
{
getRequest().setAttribute( "exceptionType",
ex.getClass().getName() );
getRequest().setAttribute( "customMessage", message );
return new Forward( "errorDetailsPage" );
}
|
在本例中,該方法在request中保存異常類型和自定義信息(在@jpf:catch中通過message-key 屬性定義),這樣errorDetails.jsp就能夠將它們顯示出來。
Global.app
定義在/WEB-INF/src/global/Global.app的Global.app既是那些無法處理的動作和異常的最后處理者,也是保存會話范圍狀態的地方。自從任何一個頁面流被請求開始,該類的一個唯一實例就被保存在用戶會話中,并且一直存在直到會話結束。
在一個頁面流中啟動一個動作,但該頁面流并不能處理這個動作,這時Global.app就會處理它。對于異常也是一樣:如果異常在某個頁面流中無法處理,Global.app將會處理它。下面的Global.app例子處理任何無法捕捉的Exception,并管理所有不由頁面流處理的“goHome”動作。
/**
* @jpf:catch type="Exception" path="/error.jsp"
*/
public class Global extends GlobalApp
{
/**
* @jpf:action
* @jpf:forward name="homePage" path="/index.jsp"
*/
public Forward goHome()
{
return new Forward( "homePage" );
}
}
|
如前所述,Global.app實例在整個用戶會話中保持激活狀態。想要從頁面流中訪問它的實例,需要做下面兩件事情:
- 在頁面流中聲明一個特殊成員變量:protected global.Global globalApp。它會被自動初始化為當前Global.app實例,并可以在頁面流的動作方法和生命周期方法(比如onCreate,beforeAction,afterAction)中使用。
- 調用PageFlowUtils.getGlobalApp( getRequest() );
可以使用“globalApp”數據綁定上下文從JSP中訪問Global.app公共成員以及getter/setter方法:
一個高級例子
下面的例子用來演示嵌套、異常處理、和Global.app。在例子中,用戶可以執行一個需要登錄的動作(“doit”)。如果用戶登錄,頁面流繼續執行“doit”動作。否則一個異常將被迫拋出并被Global.app捕獲,然后它將定向到一個嵌套登錄頁面流,在用戶登錄之后再重新返回“doit”動作。首先,我們來看看主頁面流(/demoLogin/demoLoginController.jpf):
該頁面流中唯一特殊的地方就是“doit”動作的行為:
/**
* @jpf:action login-required="true"
* @jpf:forward name="success" path="success.jsp"
*/
public Forward doit()
{
return new Forward( "success" );
} |
@jpf:action 中的login-required 屬性在用戶沒有登錄的情況下將會拋出一個NotLoggedInException 。這從邏輯上相當于明確地拋出異常:
/**
* @jpf:action
* @jpf:forward name="success" path="success.jsp"
*/
public Forward doit()
throws NotLoggedInException
{
if ( getRequest().getUserPrincipal() == null )
{
throw new NotLoggedInException();
}
return new Forward( "success" );
}
|
當從doit中拋出NotLoggedInException時,該異常將被Global.app捕獲,它將定向到嵌套頁面流/login/loginController.jpf:
/**
* @jpf:catch type="NotLoggedInException"
* path="/login/loginController.jpf"
*/
public class Global extends GlobalApp |
/login/loginController.jpf 如下:
當嵌套頁面流啟動“loginOK”動作時,它不被/demoLogin/demoLoginController.jpf 處理(仍是原頁面流),而是被交給Global.app,Global.app在當前頁面流——/demoLogin/demoLoginController.jpf——中重新運行先前的動作。
/**
* @jpf:catch type="NotLoggedInException"
* path="/login/loginController.jpf"
*/
public class Global extends GlobalApp
{
/**
* @jpf:action
* @jpf:forward name="previousAction" return-to="action"
*/
protected Forward loginOK()
{
return new Forward( "previousAction" );
}
|
@jpf:forward 中的return-to="action" 屬性使最近執行過的動作被重新運行;在本例中即是“doit”,現在它可以成功執行了。最后,如果嵌套頁面流啟動“loginCancel”動作,同樣將會交給Global.app處理,Global.app將返回到當前頁面流的前一個顯示頁面(仍然在/demoLogin/demoLoginController.jpf 中)。
/**
* @jpf:catch type="NotLoggedInException"
* path="/login/loginController.jpf"
*/
public class Global extends GlobalApp
{
/**
* @jpf:action
* @jpf:forward name="previousAction" return-to="action"
*/
protected Forward loginOK()
{
return new Forward( "previousAction" );
}
/**
* @jpf:action
* @jpf:forward name="previousPage" return-to="page"
*/
protected Forward loginCancel()
{
return new Forward( "previousPage" );
}
} |
在本例中,如果用戶取消登錄,他將重新回到起始頁面。