[問題]
使用Struts2作爲web框架,知道它的攔截器(Interceptor)機制,類似與Filter和Spring的AOP,於是實現了一個爲Action增加自定義前置(before)動作和後置動作(after)的攔截器(曰:WInterceptor),不過用一段時間發現,在WInterceptor的after中,對Action對象的屬性修改在頁面看不到,對請求對象的屬性設置也無效。爲什麼在調用了Action之後(invokeAction())之後,request就不能使用了呢,攔截器不能改變Action的Result麼?
[探幽]
在重看了Struts2的攔截器的官方文檔以後,還是不明白上面的問題是爲什麼。地球人都知道,Struts2其實就是Webwork2,而攔截器的核心實現在XWork,利用XWork的攔截器框架,Struts2在外圍通過線程上下文,綁定了Request和Response對象的包裝類,哪問題到底在Struts2,還是在XWork?
在看到下面這張調用圖,我才突然反應過來,“我真笨,真的,我只知道攔截器調用棧的最底層,是Action方法的調用,卻不知道Result的調用也是在棧底調用,之後才返回給上一個攔截器,層層退出”:
感謝這張圖的作者,它簡單,但有效。
問題的關鍵在於,在調用actionInvocation.invoke()的之後,不僅執行類Action,也執行類Result。因而,等退回到攔截器的調用代碼時,Result已經生成,View已經確定,這時你再修改模型(Action的屬性)或請求對象的屬性,對視圖不會有任何影響。
另,爲什麼Result的執行不放到攔截器鏈的外面呢?這是我開始的直覺,有知道的朋友煩告知一聲。
[解難]
方法一:使用現成的PreResultListener監聽器事件
搞清楚原因,捲起袖子幹吧,只要讓WInterpretor的after事件,放在Result的生成之前就行了。
看看XWork的攔截器接口注入的actionInvocation,其實就提供增加Result執行的前置監聽事件-PreResultListener:
/**
* Register a {@link PreResultListener} to be notified after the Action is executed and
* before the Result is executed.
* <p/>
* The ActionInvocation implementation must guarantee that listeners will be called in
* the order in which they are registered.
* <p/>
* Listener registration and execution does not need to be thread-safe.
*
* @param listener the listener to add.
*/
void addPreResultListener(PreResultListener listener);
因此,讓攔截器實現這個接口,就可以自然實現Action執行after事件了。
方法二,實現自己的 ActionInvocation ,手動分離Action和Result的執行
本來前面的方法已經很好了,可是,可是啊,在addPreResultListener裏的異常,不會被Struts的框架捕獲,而且,addPreResultListener接口不能傳遞自己的上下文參數,難道動用ThreadLocal傳參?
研究了一下XWork的ActionInvocation 接口默認實現類DefaultActionInvocation, 寫了一個包裝類,將Action的執行和Result的生成完全分開,或許有人用的着,放上來,見附件(ActionInvocationWrapper),如有不妥之處請告知。
exeucteAction是執行Action,executeResult是執行Result