tapestry的URL形如/examples/app?service=page/Admin
能
夠保證有效運行的一個非常重要的原因是,用有狀態的javabean代替無狀態的servlet構建一個tapestry應用。page是有狀態的,他只
能在一個線程里為一個用戶處理一個request,而一個servelet,沒有用戶的狀態,可以在并發線程中為任何數量同時發生的request提供服
務。使用有狀態的page遇到的問題和使用數據庫的連接遇到的問題非常相似。
engine
engine
是每個tapestry應用的中心,它是一個負責支持和組織應用所有方面的對象,他把所有小的子系統綁在一起構成一個tapestry應用,它首先負責管
理server端的狀態,管理Visit對象及持久頁面屬性,他會被保存在session中。engine對象的service()方法,負責進來的
request處理和把響應返回給客戶端。 ApplicationServlet調用public boolean
service(RequestContext context)方法figure
7.4,執行request處理,此服務不僅要進行很多的初始化工作,更重要的是它包括多級的異常捕捉、報告,任何未捕捉的異常會由異常頁來呈現。過程如
下:
1).調用AbstractEnginer的protected void
setupForRequest(RequestContext
context)方法,確保engine對象被設置,這個方法很重要,細節可參看API文檔,在覆寫類方法的子類中,必須首先第一句調用這個方法
2).調用自己的getService(String name)
3).new RequestCycle(IEngine engine, RequestContext requestContext, IEngineService service, IMonitor monitor)
4).調用IEngineService的service(IEngineServiceView engine, IRequestCycle cycle, ResponseOutputStream output)方法
5).調用RequestCycle對象的cleanup()
6).調用自己的cleanupAfterRequest(IRequestCycle cycle)方法

Engine service
Engine service是實現了IEngineService接口的對象,他包含很多創建和服務應用URL的方法,且全是在一個對象中,Engine service更象servlet,他能被很多線程共享,不能記錄客戶狀態。tapeestry默認是有9個service,4個最長用的是home, page,direct和external,可見Table 7.3,大部分service有相對應的部件

IEngineService
的service(IEngineServiceView engine, IRequestCycle cycle,
ResponseOutputStream
output)方法,IRequestCycle的一些方法調用等,各個service的調用是不相同,這些步之后各個service都要回調
IEngine對象的renderResponse()方法,處理也是一樣的。
home Service:
1).調用IRequestCycle的getPage(String name),返回home page
2).
調用IRequestCycle的activate(IPage
page)方法,此方法為request設置最終返回客戶端顯示的活動頁面,活動頁面典型的由service設置,但因為可能被替換要顯示的頁面,也會經
常被validator方法pageValidate(PageEvent
event)改變,這個方法的操作過程如下:它調用page對象的validate(IRequestCycle
cycle)方法,(validate()方法用于基本的安全驗證,這個方法實際上并不執行任何檢查Figure7.06,page對象可以有多個
PageValidateListener,The
validate()方法調用每個validator對象的pageValidate()。最通常的方法是page對象自己實現
PageValidateListener接口,會自動注冊成為自己的validator。validator可以通過throw a
PageRedirectException激活不同的頁面,當PageRedirectException異常被扔出,由service處理的
request過程將被中止,被異常指定的頁面被激活并被立馬呈現給客戶端。)
3).service對象回調engine的renderResponse()方法,將使活動頁面被呈現并響應給客戶端


page service:
除了service對象和home service不一樣,其他步驟相同
direct service:
DirectLink
和Form部件使用這個服務,這兩個部件都實現IDirect接口,當處理form的submit時,會首先執行一個rewind動作,之后執行form
指定的listener動作。direct
service能夠檢查session是否過期,DirectLink和Form部件的“stateful”屬性,默認為“false”,設置為
“true”就可以進行session檢查,當呈現響應時,direct
service會生成URL,形如:/examples/app?service=direct/1/Guess/select,URL中的1,就標明這
需要檢查session是否過期,

1).調用IRequestCycle的getPage(String name),返回page
2). 調用IRequestCycle的activate(IPage page)方法,此方法為request設置最終返回客戶端顯示的活動頁面,活動頁面典型的由service設置,但因為可能被替換要顯示的頁面,也會經 常被validator方法pageValidate(PageEvent event)改變,這個方法的操作過程如下:它調用page對象的validate(IRequestCycle cycle)方法,(validate()方法用于基本的安全驗證,這個方法實際上并不執行任何檢查,page對象可以有多個 PageValidateListener,The validate()方法調用每個validator對象的pageValidate()。最通常的方法是page對象自己實現 PageValidateListener接口,會自動注冊成為自己的validator。validator可以通過throw a PageRedirectException激活不同的頁面,當PageRedirectException異常被扔出,由service處理的 request過程將被中止,被異常指定的頁面被激活并被立馬呈現給客戶端。)
3).調用IPage的getNestedComponent(String path),返回一個IDirect對象
4).調用IDirect對象的isStateful(),如果為true,session過期檢查將要發生,檢查HttpSession過期,StaleSessionException異常將被服務扔出
5).調用IRequestCycle的setServiceParameters(Object[] parameters),由service調用,service參數被解開并存入request cycle的serviceParameters屬性中
6).調用IDirect對象的trigger(IRequestCycle cycle),調用部件的listener方法,執行相應的action
7).service對象回調engine的renderResponse()方法,將使活動頁面被呈現并響應給客戶端

需要注意的是,各方法調用順序很重要,validate()發生的比較早,那時還不能訪問service參數,session檢查發生在validate()之后
對于DirectLink部件,trigger()方法內部處理過程:
1).調用IActionListenerr的actionTriggered(IComponent component,IRequestCycle cycle)方法
1.1).通過反射機制調用listener方法

對于Form部件,trigger()方法執行,要執行rewind,內部過程Figure 7.12:
1).調用IRequestCycle的rewindForm(IForm form,String targetActionId)方法
1.1).調用page對象的beginPageRender(),觸發適當的事件
1.2).回調IForm對象的rewind(IMarkupWriter writer,IRequestCycle cycle)
1.2.1).調用IForm對象的render(IMarkupWriter writer, IRequestCycle cycle)
1.2.2).調用監聽方法等
1.3).調用page對象的endPageRender(),觸發適當的事件

external Service:
1).調用IRequestCycle的getPage(String name),返回page
2).調用IRequestCycle的setServiceParameters(Object[] parameters),由service調用
3). 調用IRequestCycle的activate(IPage page)方法,此方法為request設置最終返回客戶端顯示的活動頁面,活動頁面典型的由service設置,但因為可能被替換要顯示的頁面,也會經 常被validator方法pageValidate(PageEvent event)改變,這個方法的操作過程如下:它調用page對象的validate(IRequestCycle cycle)方法,(validate()方法用于基本的安全驗證,這個方法實際上并不執行任何檢查,page對象可以有多個 PageValidateListener,The validate()方法調用每個validator對象的pageValidate()。最通常的方法是page對象自己實現 PageValidateListener接口,會自動注冊成為自己的validator。validator可以通過throw a PageRedirectException激活不同的頁面,當PageRedirectException異常被扔出,由service處理的 request過程將被中止,被異常指定的頁面被激活并被立馬呈現給客戶端。)
3).調用IExternalPage的activateExternalPage(Object[] parameters, IRequestCycle cycle)
4).service對象回調engine的renderResponse()方法,將使活動頁面被呈現并響應給客戶端

從池中獲得一個頁面
雖然采用池化技術,因為page眾多,我認為,在最開始池中是沒有page的,只有第一次訪問生成一個完整page,直接返回給request使用,使用完畢page返回池中等待復用。
Figure
7.19 IRequestCycle的getPage(String name)方法返回一個page實例,這個實例被request
cycle對象在整個request期間緩存,將來調用同名page名字的getPage()會返回相同實例,getPage()方法內部步驟是,
1).
用IPageSource的getPage(IRequestCycle cycle, String pageName, IMonitor
monitor)方法,IPageSource就是page池,他能實例化一個新的page實例,如果池中沒有可用的page實例,page實例調用自己
的attach(IEngine value)以把自己綁定到一個具體的engineh上,一直到request cycle結束,才解除綁定返回池中。
2).調用page的setRequestCycle(IRequestCycle cycle)
3).
調用IEngine的getPageRecorder(String pageName,IRequestCycle
cycle),IPageRecorder是一個對象,負責追蹤page的持久頁面屬性變化的,當持久頁面屬性改變了,新值就會被記錄在
HttpSession中。page持久狀態是特定于一個專門用戶,完全和page實例分開的。IPageRecorder通過簡單的通知機制被鉤入
page實例,IPageRecorder觀察到持久屬性改變,就會他作為一個命名的session屬性把持久屬性值安全的保存到
HttpSession,各自的持久頁面屬性被作為獨自的HttpSession屬性保存
4).調用IPageRecorder的rollback(IPage page),將頁面持久屬性恢復到HttpSession屬性保存的值

創建一個新Page實例
當請求page,而池中又沒有實例可用,page source會利用PageLoader(是類不是IPageLoader接口)來創建一個page實例figure 7.20:
PageLoader不是threadsafe,PageSource要創建一個新的PageLoader實例為每一個要裝載的頁面,為解決多線程的問題
PageLoader的IPage loadPage(String name,
INamespace namespace,
IRequestCycle cycle,
IComponentSpecification specification)過程:
1).new一個page實例,java page class被實例化
2).初始化屬性,page的初始屬性被設置,包括page名字
3).page實例調用自己的attach(IEngine value)以把自己綁定到一個具體的engineh上
4).page包含的部件被遞歸創建,每一個部件被創建,page loader就會調用部件的finishLoad()方法,從BaseComponent類繼承的部件在這時也會裝載他的模板
5).page的finishLoad()方法被調用

IRequestCycle cycle,
IPageLoader loader,
IComponentSpecification specification);
protected void finishLoad();
盡 量重載protected的無參的finishLoad(),且無需先調用父類的方法,除非要使用到3個參數,才會重載public的,且當重載 public的帶3個參數的方法時,必須先調用父類的public的同名方法,一旦調用失敗,就會造成加載page或component的模板失敗,也會 造成其他方面的影響
把Page實例返回池
在request
結束時,response被發送回client之后,附著在request上的page必須被返回池,頁面屬性包括持久的臨時的都必須被重置回初始值,以
供其他用戶的request使用。如果保留屬性值不重置是非常危險的,因為所有的page實例是完全共享的,另一個request完全有可能從池中獲得上
一個用戶使用過的page,所以信息就會暴露。IRequestCycle的cleanup()方法Figure
7.21,會釋放所有他擁有的資源,就包括釋放page回page source,過程如下:
1).調用IPageSource的releasePage(IPage page)方法
1.1).IPage的detach()方法
1.1.1).清除changeObserved屬性,Clears the changeObserved property
1.1.2).調用所有注冊的相關監聽器的PageDetachListener.pageDetached(PageEvent)
1.1.3).調用org.apache.tapestry.AbstractPage.initialize()清除重置所有屬性
1.1.4).Clears
the engine, visit and requestCycle properties,the page's
visit,engine,and requestCycle properties are reset to null.

實際上只要你用<property-specification>元素來聲明持久或臨時屬性,就不必關心頁面的清除工作, detach()或initialize()方法主要是早期版本中要顧及的。
頁面呈現過程:
tapestry呈現的核心是IRender接口,只有一個方法:
public void render(IMarkupWriter writer, IRequestCycle cycle);
這
個接口被希望參與頁面呈現處理的所有對象所實現,他是IComponent的父接口,因此所有的部件都可以被呈現。IMarkupWriter接口一個很
重要的工作,把所有xml保留字自動轉換,如><等,不用程序來干預。BasePage類實現了
getResponseWriter(),源碼為:
public IMarkupWriter getResponseWriter(OutputStream out) {
return new HTMLWriter(out, getOutputEncoding());
}
HTMLWriter的getContentType()方法默認會返回一個字符串“text/html; charset=UTF-8”,因此若要返回xml類型,應該在BasePage的子類中覆寫getResponseWriter()方法,如下:
public IMarkupWriter getResponseWriter(OutputStream out) {
return new HTMLWriter("text/xml",getOutputEncoding(),out);
}
若要返回wml類型,應該在BasePage的子類中覆寫getResponseWriter()方法,如下:
public IMarkupWriter getResponseWriter(OutputStream out) {
return new WMLWriter(out, getOutputEncoding());
}
engine對象的renderResponse()方法:
1).調用IRequestCycle對象的getPage(),得到page對象
2).調用page對象的public IMarkupWriter getResponseWriter(OutputStream out)方法,返回一個IMarkupWriter對象
3).調用IMarkupWriter對象的getContentType(),這個值用來設置HttpServletResponse的setContentType()方法
4).
調用IRequestCycle對象的renderPage(IMarkupWriter
writer),呈現指定的頁面,應用應該總是用這個方法來呈現頁面,而不是直接調用IRender.render(IMarkupWriter,
IRequestCycle),因為在呈現之前cycle對象必須進行一些設置;
4.1).IRequestCycle對象的
renderPage()調用page對象renderPage(IMarkupWriter writer,IRequestCycle
cycle),被調用來呈現完整頁面,這個方法應該只由IRequestCycle.renderPage(IMarkupWriter
writer)調用,這個方法內執行呈現的具體過程如下Figure 7.18
4.1.1).調用PageRenderListener的pageBeginRender(org.apache.tapestry.event.PageEvent)方法
4.1.2).調用page對象的beginResponse(IMarkupWriter, IRequestCycle),這是最后一次機會可以修改持久屬性
4.1.1).回調IRequestCycle的commitPageChanges()方法,這個方法會通知負責持久屬性管理的page recorders進行相應的保存
4.1.1).調用page對象的render(IMarkupWriter, IRequestCycle),page開始呈現他的模板的內容,同樣的遞歸呈現他包含的部件
4.1.1).
調用PageRenderListener的pageEndRender(org.apache.tapestry.event.PageEvent)
(this occurs even if a previous step throws an exception).

頁面屬性
聲
明的屬性,持久的、臨時的都可以有一個初始值,初始值或者是<property-specification>元素的"initial-
value"屬性的值,或者是<property-specification>元素體的內容,初始值是OGNL表達式,此表達式只被計算一
次并且表達式的值被保存起來,是在page或component的finishLoad()方法被調用之后。表達式的值被用來給屬性賦初值,當page被
從request拆開時,為復用返回page池之前,此表達式的值被用來更新屬性。初始值也可以不在<property-
specification>元素指定,可以在finishLoad()方法內設置,finishLoad()方法調用完畢,tapestry框架
會讀這個屬性,讀出的值就將是屬性的初值,會被保存為以后用,當page拆開時返回前,此保存的初值會被重新賦給屬性,也就是說,無論是在initial
-value或者是在finishLoad()中,都可指定初值,然后此初值被tapestry保存用于為屬性恢復初值!
