JSF對通過關(guān)聯(lián)組件和事件來構(gòu)建頁面而說是非常棒的,但是,與所有現(xiàn)有的技術(shù)一樣,它需要一個(gè)控制器來分離出頁面間的導(dǎo)航?jīng)Q策,并提供到業(yè)務(wù)層的鏈接。它擁有一個(gè)基本的導(dǎo)航處理程序,可以用功能完備的處理程序來替換它。Page Flow為創(chuàng)建可重用的封裝頁面流提供了基礎(chǔ),并可以與視圖層并行工作。它是一個(gè)功能完備的導(dǎo)航處理程序,將JSF頁面作為最優(yōu)先的處理對象。本文將討論如何集成這兩種技術(shù)來利用二者的優(yōu)點(diǎn)。
構(gòu)建Beehive/JSF應(yīng)用程序
要構(gòu)建Beehive/JSF應(yīng)用程序,首先要啟動(dòng)Page Flow,然后添加對JSF的支持。起點(diǎn)是從基本的支持NetUI(Beehive中包含Page Flow的組件)的項(xiàng)目開始。根據(jù)指導(dǎo)構(gòu)建基本的支持NetUI的Web應(yīng)用程序。在本文中,我們暫且稱之為“jsf-beehive”,可以在 http://localhost:8080/jsf-beehive 上獲得。
接下來,安裝并配置JSF。Page Flow可以使用任何與JavaServer Faces 1.1兼容的實(shí)現(xiàn),并針對兩種主流實(shí)現(xiàn)進(jìn)行了測試:Apache MyFaces和JSF Reference Implementation。根據(jù)下面的指導(dǎo)在新的Web應(yīng)用程序中安裝JSF:MyFaces v1.0.9及更高版本,JSF Reference Implementation v1.1_01,或者其他實(shí)現(xiàn)。之后,可以使用WEB-INF/faces-config.xml中的一個(gè)簡單入口啟動(dòng)Page Flow集成,入口在<application>標(biāo)簽之下,<navigation-rule>標(biāo)簽之上:
<factory>
<application-factory>
org.apache.beehive.netui.pageflow.faces.PageFlowApplicationFactory
</application-factory>
</factory>
添加了這些就為頁面流提供了一個(gè)機(jī)會,使其可以提供自己的JSF框架對象版本來定制其行為。通常來說,只有在使用頁面流功能的時(shí)候,JSF行為才會被修改;JSF的基本行為不會改變。
基本集成
JSF中頁面流的最基本用處是引發(fā)(調(diào)用)來自JSF頁面的動(dòng)作。JSF頁面可以處理頁面內(nèi)事件,而頁面流動(dòng)作則是從一個(gè)頁面導(dǎo)航到另一頁面的方法。首先,在Web應(yīng)用程序中創(chuàng)建一個(gè)名為“example”的目錄,在其中創(chuàng)建一個(gè)頁面流控制器類:
package example;
import org.apache.beehive.netui.pageflow.Forward;
import org.apache.beehive.netui.pageflow.PageFlowController;
import org.apache.beehive.netui.pageflow.annotations.Jpf;
@Jpf.Controller(
simpleActions={
@Jpf.SimpleAction(name="begin", path="page1.faces")
}
)
public class ExampleController extends PageFlowController
{
@Jpf.Action(
forwards={
@Jpf.Forward(name="success", path="page2.faces")
}
)
public Forward goPage2()
{
Forward fwd = new Forward("success");
return fwd;
}
}
在這個(gè)頁面流中有兩個(gè)動(dòng)作:跳轉(zhuǎn)到page1.faces的begin動(dòng)作和跳轉(zhuǎn)到page2.faces的goPage2動(dòng)作。將goPage2作為一個(gè)方法動(dòng)作(而不是簡單動(dòng)作)的原因是稍后將會對其進(jìn)行擴(kuò)充。
在構(gòu)造頁面的時(shí)候,應(yīng)當(dāng)以.jsp為擴(kuò)展名創(chuàng)建page1和page2;JSF servlet處理每個(gè).faces請求,并最終跳轉(zhuǎn)到相關(guān)的JSP。所以,跳轉(zhuǎn)到page1.faces最終將顯示page1.jsp,如下:
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
?
<html>
<body>
<f:view>
<h:form>
<h:panelGrid>
<h:outputText value="Page 1 of page flow #{pageFlow.URI}"/>
<h:commandLink action="goPage2" value="Go to page 2"/>
</h:panelGrid>
</h:form>
</f:view>
</body>
</html>
從JSF頁面引發(fā)一個(gè)動(dòng)作很簡單:使用命令組件的action屬性中的動(dòng)作名字就可以了。在上面的例子中,commandLink指向goPage2動(dòng)作。使用頁面流集成,這意味著goPage2動(dòng)作會在example.ExampleController中運(yùn)行。
就是這樣。要試驗(yàn)的話,構(gòu)建應(yīng)用程序,點(diǎn)擊 http://localhost:8080/jsf-beehive/example/ExampleController.jpf ,這將通過begin動(dòng)作跳轉(zhuǎn)到page1.faces。單擊鏈接“Go to page 2”,會引發(fā)goPage2動(dòng)作并跳轉(zhuǎn)到page2.faces。
后臺Bean
Page Flow框架可以管理與JSF頁面相關(guān)的后臺bean(backing bean)。該類是放置與頁面相關(guān)的事件處理程序和狀態(tài)的方便場所。可以把它看作是集中放置與頁面交互時(shí)所運(yùn)行的所有代碼的單一場所。當(dāng)點(diǎn)擊一個(gè)JSF頁面時(shí),Page Flow會判斷是否有具有同樣名稱和包的類,例如,page /example/page1.faces的example.page1類。如果存在這樣的類,并且它用@Jpf.FacesBacking進(jìn)行注釋并擴(kuò)展了FacesBackingBean,它就會創(chuàng)建該類的一個(gè)實(shí)例。當(dāng)離開JSF頁面而轉(zhuǎn)到一個(gè)動(dòng)作或者其它任何頁面時(shí),后臺bean會被銷毀。后臺bean與JSF頁面共存亡。
綁定到后臺bean中的屬性
下面是page1.faces的一個(gè)非常簡單的后臺bean,以及屬性someProperty。文件名是page1.java:
package example;
import org.apache.beehive.netui.pageflow.FacesBackingBean;
import org.apache.beehive.netui.pageflow.annotations.Jpf;
@Jpf.FacesBacking
public class page1 extends FacesBackingBean
{
private String _someProperty = "This is a property value from"
+ getClass().getName() + ".";
public String getSomeProperty()
{
return _someProperty;
}
public void setSomeProperty(String someProperty)
{
_someProperty = someProperty;
}
}
在JSF頁面(page1.jsp)中,可以利用backing綁定上下文來綁定到這個(gè)屬性:
<h:outputText value="#{backing.someProperty}"/>
上面的例子顯示了someProperty(最終在后臺bean上調(diào)用getSomeProperty())的值。類似地,設(shè)置這個(gè)值:
<h:inputText value="#{backing.someProperty}"/>
注意,在這個(gè)例子中,后臺bean中沒有出現(xiàn)事件處理程序或組件引用。這就縮短了代碼;后臺bean是放置頁面所有的處理程序和組件引用的好地方。
從后臺bean引發(fā)頁面流動(dòng)作
在上面的“基本集成”部分,我們直接從JSF組件引發(fā)頁面流動(dòng)作。通常情況下,只需這樣即可;當(dāng)單擊一個(gè)按鈕或者鏈接時(shí),會運(yùn)行一個(gè)動(dòng)作并跳轉(zhuǎn)到另一個(gè)頁面上。如果想在調(diào)用控制器之前運(yùn)行一些與頁面相關(guān)的代碼,或者如果希望頁面可以在幾個(gè)動(dòng)作之間進(jìn)行動(dòng)態(tài)選擇的話,可以在命令處理程序(JSF頁面所運(yùn)行的一個(gè)Java方法)中引發(fā)一個(gè)動(dòng)作。下面是一個(gè)命令處理程序的例子,可以把它放到后臺bean page2.java中(或者其它任何可公開訪問的bean中):
public String
chooseNextPage()
{
return "goPage3";
}
這是一個(gè)非常簡單的命令處理程序,它選擇了goPage3動(dòng)作。可以用標(biāo)準(zhǔn)的JSF方式從一個(gè)JSF命令組件綁定到這個(gè)命令處理程序:
<h:commandButton action="#{backing.chooseNextPage}"
value="Submit"/>
當(dāng)單擊鏈接時(shí),會運(yùn)行chooseNextPage命令處理程序,它會選擇引發(fā)goPage3動(dòng)作。還可以對命令處理程序方法使用一個(gè)特殊的頁面流注釋——@Jpf.CommandHandler:
@Jpf.CommandHandler(
raiseActions={
@Jpf.RaiseAction(action="goPage3")
}
)
public String chooseNextPage()
{
return "goPage3";
}
該注釋使支持Beehive的工具可以知道命令處理程序引發(fā)了后臺bean中的哪個(gè)動(dòng)作,并允許擴(kuò)展JSF動(dòng)作處理的能力(參見下面“從JSF頁面向頁面流發(fā)送數(shù)據(jù)”部分)。
從后臺bean訪問當(dāng)前頁面流或共享流
在某些情況下,您或許想直接從后臺bean訪問當(dāng)前頁面流或一個(gè)活動(dòng)的共享流。為此,只需創(chuàng)建一個(gè)適當(dāng)類型的字段,并使用@Jpf.PageFlowField或@Jpf.SharedFlowField對其進(jìn)行適當(dāng)注釋:
@Jpf.CommandHandler(
raiseActions={
@Jpf.RaiseAction(action="goPage3")
}
)
public String chooseNextPage()
{
return "goPage3";
}
這些字段將在創(chuàng)建后臺bean的時(shí)候被初始化。無需手動(dòng)對其進(jìn)行初始化。下面的例子使用了自動(dòng)初始化的ExampleController字段。在這個(gè)例子中,“show hints”單選鈕的事件處理程序在頁面流中設(shè)置了一個(gè)普通優(yōu)先級。
@Jpf.PageFlowField
private ExampleController myController;
@Jpf.SharedFlowField(name="sharedFlow2") // "sharedFlow2" is a
// name defined in the
// page flow controller
private ExampleSharedFlow mySharedFlow;
在很多情況下,頁面不需要直接與頁面流或者共享流進(jìn)行交互;使用其它方法從頁面流向JSF頁面?zhèn)鬟f數(shù)據(jù)就足夠了,反之亦然。下面我將給出一些例子。
從頁面流控制器訪問后臺bean
您不能從頁面流控制器訪問后臺bean!至少,這不容易做到,這是有意為之的。后臺bean與JSF頁面緊密相關(guān),當(dāng)您離開頁面的時(shí)候,后臺bean會被銷毀。正如頁面流控制器不應(yīng)了解頁面細(xì)節(jié)一樣,它也不應(yīng)了解后臺bean。當(dāng)然了,可以從后臺bean向控制器傳遞數(shù)據(jù)(稍后將會介紹),甚至可以傳遞后臺bean實(shí)例本身,但是在大多數(shù)情況下,后臺bean的內(nèi)容是不應(yīng)當(dāng)泄露給控制器的。
生命周期方法
通常,當(dāng)后臺bean發(fā)生某些事情的時(shí)候,比如當(dāng)它被創(chuàng)建或銷毀時(shí),我們希望能運(yùn)行代碼。在Page Flow框架的生命周期中,它會對后臺bean調(diào)用一些方法:
-
onCreate():創(chuàng)建bean時(shí)
-
onDestroy():銷毀bean時(shí)(從用戶會話移除)
-
onRestore():這個(gè)需要詳細(xì)解釋一下。我說過,當(dāng)您離開頁面的時(shí)候,后臺bean會被銷毀。在大多數(shù)情況下是這樣的,但是如果頁面流使用了navigateTo特性(它使您可以再次訪問先前顯示的頁面),在您離開頁面之后,Page Flow框架會保留后臺bean一小段時(shí)間,以防它需要還原。當(dāng)通過@Jpf.Forward或@Jpf.SimpleAction使用navigateTo=Jpf.NavigateTo.currentPage或navigateTo=Jpf.NavigateTo.previousPage還原一個(gè)JSF頁面時(shí),頁面的組件樹及其后臺bean都被Page Flow框架還原。當(dāng)這種情況發(fā)生時(shí),onRestore()就被調(diào)用。
不管要在哪個(gè)時(shí)期運(yùn)行代碼,只需重寫適當(dāng)?shù)姆椒ǎ?/font>
protected void onCreate()
{
/*some create-time logic */
}
當(dāng)重寫這些方法時(shí),不需要調(diào)用空的super版本。
在JSF頁面和頁面流之間傳遞數(shù)據(jù)
現(xiàn)在我們該看看如何在JSF頁面和頁面流之間傳遞數(shù)據(jù)了。
從頁面流向JSF頁面發(fā)送數(shù)據(jù)
通常,您會想要利用頁面流的數(shù)據(jù)來初始化一個(gè)頁面。為此,可以向page2.faces的Forward添加“action outputs”:
@Jpf.Action(
forwards={
@Jpf.Forward(
name="success", path="page2.faces",
actionOutputs={
@Jpf.ActionOutput(name="message", type=String.class,required=true)
}
)
}
)
public Forward goPage2()
{
Forward fwd = new
Forward("success");
fwd.addActionOutput("message", "Got the message.");
return fwd;
}
做完這些之后,可以直接從JSF頁面或者后臺bean將該值作為頁面輸入來訪問。(如果您不喜歡鍵入冗長的注釋,可以省去斜體的。它們主要用于再次檢查添加的對象類型是否正確,確定不缺失類型。)
可以在頁面中利用JSF表示語言中的頁面流pageInput綁定上下文綁定到這個(gè)值:
<h:outputText value="#{pageInput.message}"/>
注意,可以利用pageFlow和sharedFlow綁定上下文綁定到頁面流控制器自身或者任何可用的共享流的屬性:
<h:outputText value="#{pageFlow.someProperty}"/>
<h:outputText value="#{sharedFlow.mySharedFlow.someProperty}"/>
最后,要想從后臺bean訪問頁面輸入,只需在bean類代碼中的任意地方調(diào)用getPageInput:
String message = (String) getPageInput("message");
從JSF頁面向頁面流發(fā)送數(shù)據(jù)
還可以隨著頁面流所引發(fā)的動(dòng)作發(fā)送數(shù)據(jù)。很多動(dòng)作將要求表單bean作為輸入;通常,表單bean用于從頁面獲取數(shù)據(jù)送到控制器。首先,讓我們構(gòu)建一個(gè)動(dòng)作來接收表單bean并跳轉(zhuǎn)到頁面:
@Jpf.Action(
forwards={
@Jpf.Forward(name="success", path="page3.faces")
}
)
public Forward goPage3(NameBean nameBean)
{
_userName = nameBean.getFirstName() + ' ' +
nameBean.getLastName();
return new Forward("success");
}
該動(dòng)作包含一個(gè)NameBean,它是一個(gè)將getters/setters作為其firstName和lastName屬性的表單bean類。它設(shè)置一個(gè)成員變量保存完整名字,之后跳轉(zhuǎn)到page3.faces。我們知道,可以直接從JSF頁面或者它的后臺bean引發(fā)一個(gè)動(dòng)作。在這兩種情況下,都可以向動(dòng)作發(fā)送表單bean。下面讓我們依次看看每種情況。
從后臺bean發(fā)送表單bean
要從后臺bean中的命令處理程序發(fā)送表單bean,需要使用一個(gè)特定的注釋。下面給出了page2.java中的情況:
private ExampleController.NameBean _nameBean;
protected void onCreate()
{
_nameBean = new ExampleController.NameBean();
}
public ExampleController.NameBean getName()
{
return _nameBean;
}
@Jpf.CommandHandler(
raiseActions={
@Jpf.RaiseAction(action="goPage3",
outputFormBean="_nameBean")
}
)
public String chooseNextPage()
{
return "goPage3";
}
在這個(gè)例子中,JSF頁面可以用它選擇的任何方式填充_nameBean的值(例如,通過將h:inputText值綁定到#{backing.name.firstName}和#{backing.name.lastName})。之后它使用@Jpf.RaiseAction上的outputFormBean屬性來標(biāo)記_nameBean應(yīng)當(dāng)被傳遞到動(dòng)作goPage3。
從JSF頁面發(fā)送表單bean
從JSF頁面直接發(fā)送表單bean很容易,只要您可以通過數(shù)據(jù)綁定表達(dá)式得到bean值。這是通過在commandButton組件內(nèi)部添加名為submitFormBean的h:attribute組件來實(shí)現(xiàn)的:
<h:commandButton action="#{backing.chooseNextPage}"
value="Submit directly from page">
<f:attribute name="submitFormBean" value="backing.name" />
</h:commandButton>
在這里,為了使表單bean發(fā)送到動(dòng)作goPage3,按鈕綁定到后臺bean的“name”屬性(getName)。
結(jié)束語
本文展示了如何將JSF在構(gòu)建頁面方面的豐富特性與Beehive Page Flow在控制頁面間導(dǎo)航方面的強(qiáng)大功能相結(jié)合。二者的集成非常容易,但是卻會對應(yīng)用造成深遠(yuǎn)的影響:它將JSF頁面與應(yīng)用級邏輯相分離,并把頁面帶入Page Flow所提供的功能領(lǐng)域中。JSF頁面得到了清楚的任務(wù):作為單個(gè)(如果有足夠能力的話)視圖元素參與到應(yīng)用程序的流中。文中沒有展示JSF頁面中具有事件處理功能且控制器中具有復(fù)雜的導(dǎo)航邏輯的完備應(yīng)用程序。但是隨著應(yīng)用程序的復(fù)雜程度提高,它就會更加需要責(zé)任的劃分以及頁面流添加給JSF的高級流功能。您可以花幾分鐘嘗試一下——很快您就將意識到這樣做所帶來的好處。