上一節在將闡述ModelDriven的機制時,經常提到一個名詞ValueStack。也許你會堅決果斷脫口而出,不就是值棧嗎?對,就是它,那你知道Struts爲何須要引入它?它是如何工做的?它和OGNL有何私情?若是你對以上問題的答案很模糊,可是又確實想知道答案,那麼本文將帶你去看看ValueStack的世界。
html
說Struts就不得不提MVC,全部的請求都是基於頁面(View)提交,頁面是數據最初的載體,而後提交給Controller,由Controller將數據作第一次處理,而後交給業務層作進一步的處理。下面,咱們仍是經過代碼直觀的體會一下。java
JSP頁面僞碼:數據結構
<form action="xxx/user-add.action" method="post"> username:<input type="text" name="username" /> age:<input type="text" name="age" /> groupName:<input type="text" name="group.name" /> <input type="submit" name="submit" value="添加" /> </form>
VO僞碼:ide
public class UserVO { private String username; private int age; private Group group; //getter and setter //... } public class Group { private String name; //getter and setter //... }
Action僞碼:post
public class UserAction implements ModelDriven { private User user = new User(); public String addUser() { //相應的業務邏輯 } public String queryUser() { //返回user對象 } @Override public Object getModel() { return user; } }
經過以上代碼,能夠看出來, 數據在View層是扁平的,沒有數據類型之分,均爲字符串。可是在Java中,數據有各類數據類型(age -> int),豐富的數據結構 (group -> Group)。因此數據由頁面向Java流轉時,就會出現類型如何匹配的問題。
spa
爲了解決以上的問題,Struts2採納了XWork的OGNL(後面會專門講解)作爲解決方案,在OGNL基礎上構建了OGNLValueStack機制,完美的實現了數據由View到Controller的匹配問題。code
你們必定對下面這段代碼不陌生:orm
username:<s:property value="username" /> age:<s:property value="age" /> groupName:<s:property value="group.name" />
使用Struts2的Tag經過OGNL取值,在上一節咱們講到,user對象最終被保存到了ValueStack中。若是你有興趣閱讀struts2的源碼時,你會發現ValueStack只是一個接口,而Struts2使用了OgnlValueStack做爲其默認實現。htm
ValueStack接口中聲明瞭getRoot()這樣一個接口:對象
/** * Get the CompoundRoot which holds the objects pushed onto the stack * * @return the root */ public abstract CompoundRoot getRoot();
看下CompoundRoot的實現:
public class CompoundRoot extends ArrayList { //...省略一些其餘實現 public Object peek() { return get(0); } public Object pop() { return remove(0); } public void push(Object o) { add(0, o); } }
學過數據結構的一眼就看出來,CompoundRoot具備棧的特性:先進後出。因此在s:property標籤中的OGNL表達式,最終會交給ValueStack來解析。username就是一個OGNL表達式,意思是調用root對象的getUsername()方法。Struts2將自動搜索CompoundRoot中有哪些元素(從第0個元素開始搜索),檢測這些元素是否有getUsername()方法,若是第0個元素沒有getUsername()方法,將繼續搜索第一、二、3……個元素是否有getUsername()方法。很明顯,user對象有這個方法,因此就能夠顯示username的值。既然CompoundRoot維護的是一個棧結構,那麼若是有多個元素都含有getUsername()方法的話,那麼<s:property value="username" />只會獲得最後一個壓入棧的元素對應的username的值。
在講ValueStack的過程當中,OGNL一直伴隨左右,那麼什麼是OGNL呢?官方的解釋就是:Object Graph Navigation Language,是一種表達式語言。使用這種表達式語言,你能夠經過某種表達式語法,存取Java對象樹中的任意屬性、調用Java對象樹的方法、同時可以自動實現必要的類型轉化。若是咱們把表達式看作是一個帶有語義的字符串,那麼OGNL無疑成爲了這個語義字符串與Java對象之間溝通的橋樑。下面經過一個簡單的程序看看OGNL的用法。
public class OgnlTest { @Test public void test01() { //建立root對象 User u = new User(); u.setUsername("javer"); u.setAge(18); Group g = new Group(); g.setName("group01"); u.setGroup(g); //建立context Map ctx = new HashMap(); ctx.put("context", "get from context:"); //直接取root對象 Object username = Ognl.getValue(Ognl.parseExpression("username"), u); AssertEquals("javer", username); //true //從上下文取值 Object value = Ognl.getValue(Ognl.parseExpression("#context", ctx, u): AssertEquals("get from context:", value); //true //從上下文取root對象 Object value = Ognl.getValue(Ognl.parseExpression("#context + username", ctx, u); AssertEquals("get from context:javer", value); } }
上面這個例子其實涵蓋了OGNL的三大要點:
1. Expression:整個OGNL的核心,全部的OGNL操做都是針對表達式的解析後進行的。表達式會規定這次OGNL操做到底要幹什麼。
2. Root Object:OGNL的操做對象,即指定到底「對誰幹」。
3. Context:OGNL的內部,全部的操做都會在一個特定的環境中運行,這個環境就是OGNL的上下文環境,將規定OGNL的操做「在哪裏幹」。
回過頭來再看看ValueStack,值棧中的數據,也是分兩個部分存放:root和context。若是某個OGNL表達式被傳遞給ValueStack(即調用ValueStack的setValue或findValue方法),而表達式中包含有對root對象的訪問操做,ValueStack將依次從棧頂往棧底搜索CompoundRoot對象中所包含的對象,看哪一個對象具備相應的屬性,找到以後,馬上返回。
因此ValueStack就是對OGNL進行了封裝,加強了部分功能。說白了,ValueStack其實就是OGNL的一個擴展,在OGNL只支持一個root的基礎上擴展到了支持多個root對象。說到這裏,可能你也恍然 大悟,Action就是一個root對象,在不使用ModelDriven的時候,頁面上的元素不就是和Action對象中的成員變量一一對應嗎!這下兩者的關係你心中也有數了吧。