一、值棧是什麼
簡單的說:值棧是對應每一個請求對象的輕量級的記憶體資料中心。
Struts2中一個很激動人心的特性就是引入了值棧,在這裡統一管理著資料,供Action、Result、Interceptor等Struts2的其他部分使用,這樣一來,資料被集中管理起來而不會淩亂,大大方便了程式編寫。
Struts2中關於值棧的另外一個很激動人心的特性就是:大多數情況下,你根本無需關心值棧,你不用管它在哪裡,不用管它裡面有什麼,你只需要去擷取自己需要的資料就可以了。也就是說,你可以隱式的使用值棧。
當然,如果編寫自訂的Result或攔截器等較複雜功能的時候,還是需要顯示訪問值棧的,因此,還是需要你掌握值棧的知識。
二、值棧能幹什麼
簡單的說,值棧能夠安全執行緒的為每個請求提供公用的資料存取服務。
當有請求到達的時候,Struts2會為每個請求建立一個新的值棧,也就是說,值棧和請求是一一對應的,不同的請求,值棧也不一樣,而值棧封裝了一次請求所有需要操作的相關的資料。
正是因為值棧和請求的對應關係,因而值棧能保證安全執行緒的為每個請求提供公用的資料存取服務。
三、值棧有什麼
事實上,到現在為止,我們一直在講“值棧”,這種說法其實是不夠準確的。為什麼呢。因為在Struts2中,值棧又有廣義和狹義之分:
1:狹義值棧
通常指的是實現com.opensymphony.xwork2.util.ValueStack介面的對象,目前就是com.opensymphony.xwork2.ognl.OgnlValueStack對象。
狹義值棧主要用來存取動態EL(運算式語言)運算需要的值和結果,當然OgnlValueStack對象主要是用來支援OGNL(對象圖導航語言)運算的。
狹義值棧裡面存放著一些OGNL可以存取訪問的資料,典型如:
Action的執行個體,這樣就可以通過OGNL來訪問Action執行個體中的屬性的值了
OGNL運算式運算的值,可以設定到值棧中,可以主動訪問值棧對象,強行設定
OGNL運算式產生的中間變數,比如在後面使用Struts2的標籤的時候,使用迴圈標籤,自然會有迴圈的變數,這些都存放在值棧中
2:廣義值棧
通常指的是ActionContext對象,ActionContext是Action啟動並執行上下文,每個ActionContext是一個基本的容器,包含著Action運行需要的資料,比如請求參數、會話等。
ActionContext是安全執行緒的,每個線程有一個獨立的ActionContext,這樣你就不用擔心值棧中值的安全執行緒問題了。
ActionContext裡面存放有很多的值,典型如:
Request的parameters:請求中的參數,要注意這裡的資料是從請求對象裡面拷貝出來的,因此這裡資料的變化是不會影響到請求對象裡面的參數的值的
Request的Attribute:請求中的屬性,這裡其實就是個Map,存放著請求對象的屬性資料,這些資料和請求對象的Attribute是連動的
Session的Attribute:會話中的屬性,這裡其實就是個Map,存放著會話對象的屬性資料,這些資料和會話對象的Attribute是連動的
Application的Attribute:應用中的屬性,這裡其實就是個Map,存放著應用對象的屬性資料,這些資料和應用對象的Attribute是連動的
Value stack:也就是狹義值棧,ActionContext以value stack作為被OGNL訪問的根,簡單點說,OGNL在沒有特別指明的情況下,訪問的就是value stack裡面的資料
attr:在所有的屬性範圍中擷取值,依次搜尋page、request、session和application。
前面已經瞭解到Xwork與Web是無關的,因此Action不用去依賴於任何Web容器,不用和Servlet 的API去互動,但是Action需要能訪問到Web應用的資料,不僅僅是取得請求參數的值,往往也需要在Action裡直接擷取請求或會話的一些資料,對於這些資料,現在都可以通過ActionContext來擷取到。
3:關於廣義和狹義
你會看到,在ActionContext裡面其實是包含著狹義值棧的,正是因為這個原因,再加上ActionContext還包含其他的資料,因此把ActionContext稱為廣義值棧。
今後在說值棧的時候,沒有特別指明的情況下,多數就是指的廣義值棧,反正開發的時候都是說從值棧中擷取值。當然,有一種情況除外,就是在頁面上使用OGNL的時候,沒有特殊標識的情況下,預設是從value statck中取值的。
四、 ActionContext的基本使用
前面學習了值棧的基本知識,接下來,看看在程式中具體如何使用值棧。
1:如何擷取
要擷取ActionContext有兩個基本的方法,如果在不能擷取到ActionInvocation的地方,可以直接使用ActionContext一個靜態getContext方法,就可以訪問到當前的ActionContext了,樣本如下:
ActionContext ctx = ActionContext.getContext();
如果在能擷取到ActionInvocation的地方,比如在攔截器裡面、自訂的Result裡面等,可以通過ActionInvocation來擷取到ActionContext,樣本如下:
ActionContext ctx = actionInvocation.getInvocationContext();
2:擷取過後,如何使用
ActionContext主要的功能是用來存放資料的,典型的方法如下: get(String key):根據key從ActionContext當前的儲存空間裡面擷取相應的值 put(String key, Object value):把值儲存在ActionContext的儲存空間裡面 Map<String,Object> getApplication():返回ServletContext中儲存的值 Map<String,Object> getSession():返回HttpSession中儲存的值 Map<String,Object> getContextMap():返回當前context儲存的值 Map<String,Object> getParameters():返回HttpServletRequest對象裡面儲存的,用戶端提交的參數 ValueStack getValueStack():擷取OGNL的值棧
對於getXXX的方法,都有對應的setXXX方法,這裡就不去贅述了,具體的請參看Struts2的API文檔。 五、ValueStack的基本使用
在上一小節中,看到了ValueStack被包含在ActionContext中,ValueStack也是用來儲存物件的,但是它主要是通過OGNL運算式來訪問,也就是說,在Struts2裡面主要是通過標籤來訪問的。
ValueStack有一個特點,如果訪問的值棧裡有多個對象,且相同的屬性在多個對象中同時出現,則值棧會按照從棧頂到棧底的順序,尋找第一個匹配的對象。
1:如何擷取
直接由ActionContext對象的getValueStack()方法即可擷取
2:如何使用
ValueStack主要的功能也是用來存放資料的,典型的方法如下: Object findValue(String expr):根據運算式在value stack中,按照預設的訪問順序去擷取運算式對應的值 void setValue(String expr, Object value):根據運算式,按照預設的訪問順序,向value stack中設定值 Object peek():擷取value stack中的頂層對象,不修改value stack對象 Object pop():擷取value stack中的頂層對象,並把這個對象從value stack中移走 void push(Object o):把對象加入到value stack對象中,並設定成為頂層對象
3:應用樣本
前面的樣本中,歡迎頁面顯示的帳號,是從登入頁面填寫並傳遞到背景資料,假如現在想要修改在歡迎頁面顯示的帳號資料,但是前面從登入頁面填寫並傳遞到背景資料不需要變化,那麼該怎麼實現呢。
先來分析一下,要想修改result頁面顯示的值,肯定需要在Result處理之前修改這個值,否則等Result處理完成過後再改就沒有意義了。因此,可以選用PreResultListener的技術,在裡面把值修改好,然後再進行Result處理。
另外一點,在歡迎頁面是通過標籤來擷取帳號的資料並展示的,也就是說值的來源是value stack,因此,在PreResultListener裡面要修改的就是value stack裡面的值。
好了,清楚該幹什麼過後,來具體看看樣本。
(1)先來實現PreResultListener,在裡面修改value stack裡面的值,樣本如下:
public class MyPreResult implements PreResultListener{ 02. public void beforeResult(ActionInvocation actionInvocation, String result) { 03. System.out.println("現在處理Result執行前的功能,result="+result); 04. actionInvocation.getInvocationContext().getValueStack().setValue("account", "被修改了"); 05. } 06.} (2)實現了PreResultListener,還需要在運行之前註冊,這裡選擇在Action裡面來註冊這個監聽器,樣本如下:
public class HelloWorldAction extends ActionSupport { 02. private String account; 03. private String password; 04. private String submitFlag; 05. public String execute() throws Exception { 06. this.businessExecute(); 07. ActionContext c = ActionContext.getContext(); 08. MyPreResult pr = new MyPreResult(); 09. c.getActionInvocation().addPreResultListener(pr); 10. return "toWelcome"; 11. } 12. /** 13. * 樣本方法,表示可以執行商務邏輯處理的方法, 14. */ 15. public void businessExecute(){ 16. System.out.println("使用者輸入的參數為==="+"account="+account+",password="+password+",submitFlag="+submitFlag); 17. } 18. //屬性對應的getter/setter方法,省略了 19.}
(3)相應的struts.xml就比較簡單了,樣本如下:
01.<package name="helloworld" extends="struts-default"> 02. <action name="helloworldAction" class="cn.javass.action.action.HelloWorldAction"> 03. <result name="toWelcome">/s2impl/welcome.jsp</result> 04. </action> 05. </package>
(4)登入頁面沒有變動,樣本如下:
01.<form action="/helloworld/helloworldAction.action" method="post"> 02. <input type="hidden" name="submitFlag" value="login"/> 03. 帳號:<input type="text" name="account"><br> 04. 密碼:<input type="password" name="password"><br> 05. <input type="submit" value="提交"> 06.</form>
(5)再看看歡迎頁面,也沒有變動,樣本如下:
<%@ taglib prefix="s" uri="/struts-tags"%> 02.歡迎帳號為<s:property value="account"/>的朋友來訪
(6)去運行測試看看,歡迎頁面顯示出來的就應該是修改過後的值了,如下圖所示:
由於通常情況下,向value stack裡面壓入值都是由Struts2去完成,而訪問value stack多是通過標籤中的OGNL運算式,因而直接使用ValueStack的機會並不是很多。