太陽雨

          痛并快樂著

          BlogJava 首頁 新隨筆 聯(lián)系 聚合 管理
            67 Posts :: 3 Stories :: 33 Comments :: 0 Trackbacks
          首先讓我們花費1分鐘的時間來簡單思考一個問題,MVC這3者之間,到底是通過什么真正融合起來的?

          有人說是Controller,因為它是核心控制器,沒有Controller,MVC就無從談起,失去了職責劃分的原本初衷。也有人說是View,因為所有的需求都是頁面驅(qū)動的,沒有頁面,就沒有請求,沒有請求,也談不上控制器和數(shù)據(jù)模型。

          個人觀點:貫穿MVC模型之間起到粘合劑作用的是數(shù)據(jù)。數(shù)據(jù)在View層成為了展示的內(nèi)容,而在Controller層,成為了操作的載體,所以數(shù)據(jù)和整個MVC的核心。
          無論MVC三者之間的粘合劑到底是什么,數(shù)據(jù)在各個層次之間進行流轉(zhuǎn)是一個不爭的事實。而這種流轉(zhuǎn),也就會面臨一些困境,這些困境,是由于數(shù)據(jù)在不同世界中的表現(xiàn)形式不同而造成的:

          1. 數(shù)據(jù)在頁面上是一個扁平的,不帶數(shù)據(jù)類型的字符串,無論你的數(shù)據(jù)結(jié)構有多復雜,數(shù)據(jù)類型有多豐富,到了展示的時候,全都一視同仁的成為字符串在頁面上展現(xiàn)出來。

          2. 數(shù)據(jù)在Java世界中可以表現(xiàn)為豐富的數(shù)據(jù)結(jié)構和數(shù)據(jù)類型,你可以自行定義你喜歡的類,在類與類之間進行繼承、嵌套。我們通常會把這種模型稱之為復雜的對象樹。

          此時,如果數(shù)據(jù)在頁面和Java世界中互相流轉(zhuǎn)傳遞,就會顯得不匹配。所以也就引出了幾個需要解決的問題:

          1. 當數(shù)據(jù)從View層傳遞到Controller層時,我們應該保證一個扁平而分散在各處的數(shù)據(jù)集合能以一定的規(guī)則設置到Java世界中的對象樹中去。同時,能夠聰明的進行由字符串類型到Java中各個類型的轉(zhuǎn)化。

          2. 當數(shù)據(jù)從Controller層傳遞到View層時,我們應該保證在View層能夠以某些簡易的規(guī)則對對象樹進行訪問。同時,在一定程度上控制對象樹中的數(shù)據(jù)的顯示格式。

          如果我們稍微深入一些來思考這個問題,我們就會發(fā)現(xiàn),解決數(shù)據(jù)由于表現(xiàn)形式的不同而發(fā)生流轉(zhuǎn)不匹配的問題對我們來說其實并不陌生。同樣的問題會發(fā)生在Java世界與數(shù)據(jù)庫世界中,面對這種對象與關系模型的不匹配,我們采用的解決方法是使用ORM框架,例如Hibernate,iBatis等等。那么現(xiàn)在,在Web層同樣也發(fā)生了不匹配,所以我們也需要使用一些工具來幫助我們解決問題。

          在這里,我們主要討論的,是數(shù)據(jù)從View層傳遞到Controller層時的解決方案,而數(shù)據(jù)從Controller層傳遞到View層的解決方案,我們將在Struts2的Result章節(jié)重點討論。

           

          為了解決數(shù)據(jù)從View層傳遞到Controller層時的不匹配性,Struts2采納了XWork的OGNL方案。并且在OGNL的基礎上,構建了OGNLValueStack的機制,從而比較完美的解決了數(shù)據(jù)流轉(zhuǎn)中的不匹配性。

          OGNL(Object Graph Navigation Language),是一種表達式語言。使用這種表達式語言,你可以通過某種表達式語法,存取Java對象樹中的任意屬性、調(diào)用Java對象樹的方法、同時能夠自動實現(xiàn)必要的類型轉(zhuǎn)化。如果我們把表達式看做是一個帶有語義的字符串,那么OGNL無疑成為了這個語義字符串與Java對象之間溝通的橋梁。

          如何使用OGNL

          讓我們先研究一下OGNL的API,他來自于Ognl的靜態(tài)方法:
          1.   
          2. /**  
          3.  * Evaluates the given OGNL expression tree to extract a value from the given root  
          4.  * object. The default context is set for the given context and root via  
          5.  * <CODE>addDefaultContext()</CODE>.  
          6.  *  
          7.  * @param tree the OGNL expression tree to evaluate, as returned by parseExpression()  
          8.  * @param context the naming context for the evaluation  
          9.  * @param root the root object for the OGNL expression  
          10.  * @return the result of evaluating the expression  
          11.  * @throws MethodFailedException if the expression called a method which failed  
          12.  * @throws NoSuchPropertyException if the expression referred to a nonexistent property  
          13.  * @throws InappropriateExpressionException if the expression can't be used in this context  
          14.  * @throws OgnlException if there is a pathological environmental problem  
          15.  */  
          16. public static Object getValue( Object tree, Map context, Object root ) throws OgnlException;   
          17.   
          18. /**  
          19.  * Evaluates the given OGNL expression tree to insert a value into the object graph  
          20.  * rooted at the given root object.  The default context is set for the given  
          21.  * context and root via <CODE>addDefaultContext()</CODE>.  
          22.  *  
          23.  * @param tree the OGNL expression tree to evaluate, as returned by parseExpression()  
          24.  * @param context the naming context for the evaluation  
          25.  * @param root the root object for the OGNL expression  
          26.  * @param value the value to insert into the object graph  
          27.  * @throws MethodFailedException if the expression called a method which failed  
          28.  * @throws NoSuchPropertyException if the expression referred to a nonexistent property  
          29.  * @throws InappropriateExpressionException if the expression can't be used in this context  
          30.  * @throws OgnlException if there is a pathological environmental problem  
          31.  */  
          32. public static void setValue( Object tree, Map context, Object root, Object value ) throws OgnlException  


          我們可以看到,OGNL的API其實相當簡單,你可以通過傳遞三個參數(shù)來實現(xiàn)OGNL的一切操作。而這三個參數(shù),被我稱為OGNL的三要素。

          那么運用這個API,我們能干點什么呢?跑個測試看看結(jié)果:

          1.   
          2. /**  
          3.  * @author Downpour  
          4.  */  
          5. public class User {   
          6.        
          7.     private Integer id;   
          8.        
          9.     private String name;   
          10.        
          11.     private Department department = new Department();   
          12.        
          13.     public User() {   
          14.            
          15.     }   
          16.            
          17.         // setter and getters   
          18. }   
          19.   
          20. //=========================================================================   
          21.   
          22. /**  
          23.  * @author Downpour  
          24.  */  
          25. public class Department {   
          26.        
          27.     private Integer id;   
          28.        
          29.     private String name;   
          30.        
          31.     public Department() {   
          32.            
          33.     }   
          34.            
          35.         // setter and getters   
          36. }   
          37.   
          38. //=========================================================================   
          39.   
          40. /**  
          41.  * @author Downpour  
          42.  */  
          43. public class OGNLTestCase extends TestCase {   
          44.        
          45.     /**  
          46.      *   
          47.      * @throws Exception  
          48.      */  
          49.     @SuppressWarnings("unchecked")   
          50.     @Test  
          51.     public void testGetValue() throws Exception {   
          52.            
          53.         // Create root object   
          54.         User user = new User();   
          55.         user.setId(1);   
          56.         user.setName("downpour");   
          57.   
          58.         // Create context   
          59.         Map context = new HashMap();   
          60.         context.put("introduction","My name is ");   
          61.            
          62.         // Test to directly get value from root object, with no context   
          63.         Object name = Ognl.getValue(Ognl.parseExpression("name"), user);   
          64.         assertEquals("downpour",name);   
          65.            
          66.         // Test to get value(parameter) from context   
          67.         Object contextValue = Ognl.getValue(Ognl.parseExpression("#introduction"), context, user);   
          68.         assertEquals("My name is ", contextValue);   
          69.            
          70.         // Test to get value and parameter from root object and context   
          71.         Object hello = Ognl.getValue(Ognl.parseExpression("#introduction + name"), context, user);   
          72.         assertEquals("My name is downpour",hello);   
          73.                        
          74.     }   
          75.   
          76.     /**  
          77.      *   
          78.      * @throws Exception  
          79.      */  
          80.     @SuppressWarnings("unchecked")   
          81.     @Test  
          82.     public void testSetValue() throws Exception {   
          83.            
          84.         // Create root object   
          85.         User user = new User();   
          86.         user.setId(1);   
          87.         user.setName("downpour");   
          88.            
          89.                 // Set value according to the expression   
          90.         Ognl.setValue("department.name", user, "dev");   
          91.         assertEquals("dev", user.getDepartment().getName());   
          92.            
          93.     }   
          94.        
          95.   
          96. }  


          我們可以看到,簡單的API,就已經(jīng)能夠完成對各種對象樹的讀取和設值工作了。這也體現(xiàn)出OGNL的學習成本非常低。

          在上面的測試用例中,需要特別強調(diào)進行區(qū)分的,是在針對不同內(nèi)容進行取值或者設值時,OGNL表達式的不同。

          Struts2 Reference 寫道
          The framework uses a standard naming context to evaluate OGNL expressions. The top level object dealing with OGNL is a Map (usually referred as a context map or context). OGNL has a notion of there being a root (or default) object within the context. In expression, the properties of the root object can be referenced without any special "marker" notion. References to other objects are marked with a pound sign (#).


          上面這段內(nèi)容摘自Struts2的Reference,我把這段話總結(jié)為以下2條規(guī)則:

          A) 針對根對象(Root Object)的操作,表達式是自根對象到被訪問對象的某個鏈式操作的字符串表示。
          B) 針對上下文環(huán)境(Context)的操作,表達式是自上下文環(huán)境(Context)到被訪問對象的某個鏈式操作的字符串表示,但是必須在這個字符串的前面加上#符號,以表示與訪問根對象的區(qū)別。

          上面的這點區(qū)別咋看起來非常容易理解,不過一旦放到特定的環(huán)境中,就會顯示出其重要性,它可以解釋很多Struts2在頁面展示上取值的各種復雜的表達式的現(xiàn)象。這一點在下一篇文章中會進行具體的分析。

          OGNL三要素

          我把傳入OGNL的API的三個參數(shù),稱之為OGNL的三要素。OGNL的操作實際上就是圍繞著這三個參數(shù)而進行的。

          1. 表達式(Expression)

          表達式是整個OGNL的核心,所有的OGNL操作都是針對表達式的解析后進行的。表達式會規(guī)定此次OGNL操作到底要干什么

          我們可以看到,在上面的測試中,name、department.name等都是表達式,表示取name或者department中的name的值。OGNL支持很多類型的表達式,之后我們會看到更多。

          2. 根對象(Root Object)

          根對象可以理解為OGNL的操作對象。在表達式規(guī)定了“干什么”以后,你還需要指定到底“對誰干”

          在上面的測試代碼中,user就是根對象。這就意味著,我們需要對user這個對象去取name這個屬性的值(對user這個對象去設置其中的department中的name屬性值)。

          3. 上下文環(huán)境(Context)

          有了表達式和根對象,我們實際上已經(jīng)可以使用OGNL的基本功能。例如,根據(jù)表達式對根對象進行取值或者設值工作。

          不過實際上,在OGNL的內(nèi)部,所有的操作都會在一個特定的環(huán)境中運行,這個環(huán)境就是OGNL的上下文環(huán)境(Context)。說得再明白一些,就是這個上下文環(huán)境(Context),將規(guī)定OGNL的操作“在哪里干”

          OGNL的上下文環(huán)境是一個Map結(jié)構,稱之為OgnlContext。上面我們提到的根對象(Root Object),事實上也會被加入到上下文環(huán)境中去,并且這將作為一個特殊的變量進行處理,具體就表現(xiàn)為針對根對象(Root Object)的存取操作的表達式是不需要增加#符號進行區(qū)分的。

          OgnlContext不僅提供了OGNL的運行環(huán)境。在這其中,我們還能設置一些自定義的parameter到Context中,以便我們在進行OGNL操作的時候能夠方便的使用這些parameter。不過正如我們上面反復強調(diào)的,我們在訪問這些parameter時,需要使用#作為前綴才能進行。

          OGNL與模板

          我們在嘗試了OGNL的基本操作并了解了OGNL的三要素之后,或許很容易把OGNL的操作與模板聯(lián)系起來進行比較。在很多方面,他們也的確有著相似之處。

          對于模板,會有一些普通的輸出元素,也有一些模板語言特殊的符號構成的元素,這些元素一旦與具體的Java對象融合起來,就會得到我們需要的輸出結(jié)果。

          而OGNL看起來也是非常的類似,OGNL中的表達式就雷同于模板語言的特殊符號,目的是針對某些Java對象進行存取。而OGNL與模板都將數(shù)據(jù)與展現(xiàn)分開,將數(shù)據(jù)放到某個特定的地方,具體來說,就是Java對象。只是OGNL與模板的語法結(jié)構不完全相同而已。

           

          在了解了OGNL的API和基本操作以后,我們來深入到OGNL的內(nèi)部來看看,挖掘一些更加深入的知識。

          OGNL表達式

          OGNL支持各種紛繁復雜的表達式。但是最最基本的表達式的原型,是將對象的引用值用點串聯(lián)起來,從左到右,每一次表達式計算返回的結(jié)果成為當前對象,后面部分接著在當前對象上進行計算,一直到全部表達式計算完成,返回最后得到的對象。OGNL則針對這條基本原則進行不斷的擴充,從而使之支持對象樹、數(shù)組、容器的訪問,甚至是類似SQL中的投影選擇等操作。

          接下來我們就來看看一些常用的OGNL表達式:

          1. 基本對象樹的訪問

          對象樹的訪問就是通過使用點號將對象的引用串聯(lián)起來進行。

          例如:name,department.name,user.department.factory.manager.name

          2. 對容器變量的訪問

          對容器變量的訪問,通過#符號加上表達式進行。

          例如:#name,#department.name,#user.department.factory.manager.name

          3. 使用操作符號

          OGNL表達式中能使用的操作符基本跟Java里的操作符一樣,除了能使用 +, -, *, /, ++, --, ==, !=, = 等操作符之外,還能使用 mod, in, not in等。

          4. 容器、數(shù)組、對象

          OGNL支持對數(shù)組和ArrayList等容器的順序訪問:

          例如:group.users[0]

          同時,OGNL支持對Map的按鍵值查找:

          例如:#session['mySessionPropKey']

          不僅如此,OGNL還支持容器的構造的表達式:

          例如:{"green", "red", "blue"}構造一個List,#{"key1" : "value1", "key2" : "value2", "key3" : "value3"}構造一個Map

          你也可以通過任意類對象的構造函數(shù)進行對象新建:

          例如:new java.net.URL("http://localhost/")

          5. 對靜態(tài)方法或變量的訪問

          要引用類的靜態(tài)方法和字段,他們的表達方式是一樣的@class@member或者@class@method(args):

          例如:@com.javaeye.core.Resource@ENABLE,@com.javaeye.core.Resource@getAllResources

          6. 方法調(diào)用

          直接通過類似Java的方法調(diào)用方式進行,你甚至可以傳遞參數(shù):

          例如:user.getName(),group.users.size(),group.containsUser(#requestUser)

          7. 投影和選擇


          OGNL支持類似數(shù)據(jù)庫中的投影(projection) 和選擇(selection)。

          投影就是選出集合中每個元素的相同屬性組成新的集合,類似于關系數(shù)據(jù)庫的字段操作。投影操作語法為 collection.{XXX},其中XXX 是這個集合中每個元素的公共屬性。

          例如:group.userList.{username}將獲得某個group中的所有user的name的列表。

          選擇就是過濾滿足selection 條件的集合元素,類似于關系數(shù)據(jù)庫的紀錄操作。選擇操作的語法為:collection.{X YYY},其中X 是一個選擇操作符,后面則是選擇用的邏輯表達式。而選擇操作符有三種:
          ? 選擇滿足條件的所有元素
          ^ 選擇滿足條件的第一個元素
          $ 選擇滿足條件的最后一個元素

          例如:group.userList.{? #this.name != null}將獲得某個group中user的name不為空的user的列表。

          上述的所有的表達式,只是對OGNL所有表達式的大概的一個概括,除此之外,OGNL還有更多的表達式,例如lamba表達式等等。最具體的表達式的文檔,大家可以參考OGNL自帶的文檔:

          http://www.ognl.org/2.6.9/Documentation/html/LanguageGuide/apa.html

          在撰寫時,我也參考了potain同學的XWork教程以及一些網(wǎng)絡上的一些文章,特此列出:

          http://www.lifevv.com/java/doc/20071018173750030.html

          http://blog.csdn.net/ice_fire2008/archive/2008/05/12/2438817.aspx

          OGNLContext

          OGNLContext就是OGNL的運行上下文環(huán)境。OGNLContext其實是一個Map結(jié)構,如果查看一下它的源碼,就會發(fā)現(xiàn),它其實實現(xiàn)了java.utils.Map的接口。當你在調(diào)用OGNL的取值或者設值的方法時,你可能會自己定義一個Context,并且將它傳遞給方法。事實上,你所傳遞進去的這個Context,會在OGNL內(nèi)部被轉(zhuǎn)化成OGNLContext,而你傳遞進去的所有的鍵值對,也會被OGNLContext接管維護,這里有點類似一個裝飾器,向你屏蔽了一些其內(nèi)部的實現(xiàn)機理。

          在OGNLContext的內(nèi)部維護的東西很多,其中,我挑選2個比較重要的提一下。一個是你在調(diào)用方法時傳入的Context,它會被維護在OGNL內(nèi)部,并且作為存取變量的基礎依據(jù)。另外一個,是在Context內(nèi)部維護了一個key為root的值,它將規(guī)定在OGNLContext進行計算時,哪個元素被指定為根對象。其在進行存取時,將會被特殊對待。

          this指針

          我們知道,OGNL表達式是以點進行串聯(lián)的一個字符串鏈式表達式。而這個表達式在進行計算的時候,從左到右,每一次表達式計算返回的結(jié)果成為當前對象,并繼續(xù)進行計算,直到得到計算結(jié)果。每次計算的中間對象都會放在一個叫做this的變量里面這個this變量就稱之為this指針。

          例如:group.userList.size().(#this+1).toString()

          在這個例子中,#this其實就是group.userList.size()的計算結(jié)構。

          使用this指針,我們就可以在OGNL表達式中進行一些簡單的計算,從而完成我們的計算邏輯,而this指針在lamba表達式的引用中尤為廣泛,有興趣的讀者可以深入研究OGNL自帶的文檔中l(wèi)amba表達式的章節(jié)。

          默認行為和類型轉(zhuǎn)化

          在我們所講述的所有的OGNL的操作中,實際上,全部都忽略了OGNL內(nèi)部幫助你完成的很多默認行為和類型轉(zhuǎn)化方面的工作。

          我們來看一下OGNL在進行操作初始化時候的一個函數(shù)簽名:

          1.   
          2. /**  
          3.  * Appends the standard naming context for evaluating an OGNL expression  
          4.  * into the context given so that cached maps can be used as a context.  
          5.  *  
          6.  * @param root the root of the object graph  
          7.  * @param context the context to which OGNL context will be added.  
          8.  * @return Context Map with the keys <CODE>root</CODE> and <CODE>context</CODE>  
          9.  *         set appropriately  
          10.  */  
          11. public static Map addDefaultContext( Object root, ClassResolver classResolver, TypeConverter converter, MemberAccess memberAccess, Map context );  


          可以看到,在初始化時,OGNL還需要額外初始化一個類型轉(zhuǎn)化的接口和一些其他的信息。只不過這些默認行為,由OGNL的內(nèi)部屏蔽了。

          一旦需要自己定義針對某個特定類型的類型轉(zhuǎn)化方式,你就需要實現(xiàn)TypeConverter接口,并且在OGNL中進行注冊。

          同時,如果需要對OGNL的許多默認行為做出改變,則需要通過設置OGNL的全局環(huán)境變量進行。

          上述的這些內(nèi)容,有些會在后面的章節(jié)涉及,有興趣的讀者,也可以參閱OGNL的源碼和OGNL的文檔尋求幫助。
          posted on 2010-02-01 10:04 小蟲旺福 閱讀(362) 評論(0)  編輯  收藏 所屬分類: javaEE
          主站蜘蛛池模板: 宁陕县| 罗田县| 东丽区| 嘉禾县| 仙居县| 金塔县| 全南县| 独山县| 太仆寺旗| 安龙县| 望都县| 东城区| 衡水市| 凤山市| 育儿| 吴堡县| 汝南县| 道孚县| 金寨县| 南陵县| 准格尔旗| 大港区| 无棣县| 页游| 鱼台县| 潍坊市| 达孜县| 晋州市| 十堰市| 湟源县| 平原县| 喜德县| 新疆| 舒兰市| 嘉禾县| 汝南县| 资中县| 浦县| 桃江县| 桂东县| 资溪县|