關鍵字: struts2 ognl valuestack
專欄地址:http://www.javaeye.com/wiki/struts2/1353-ognl-catalyst-for-data-operation-in-struts2
首先讓我們花費1分鐘的時間來簡單思考一個問題,MVC這3者之間,到底是通過什么真正融合起來的?
有人說是Controller,因為它是核心控制器,沒有Controller,MVC就無從談起,失去了職責劃分的原本初衷。也有人說是View,因為所有的需求都是頁面驅動的,沒有頁面,就沒有請求,沒有請求,也談不上控制器和數據模型。
個人觀點:貫穿MVC模型之間起到粘合劑作用的是數據。數據在View層成為了展示的內容,而在Controller層,成為了操作的載體,所以數據是整個MVC的核心。
流轉的數據
無論MVC三者之間的粘合劑到底是什么,數據在各個層次之間進行流轉是一個不爭的事實。而這種流轉,也就會面臨一些困境,這些困境,是由于數據在不同世界中的表現形式不同而造成的:
1. 數據在頁面上是一個扁平的,不帶數據類型的字符串,無論你的數據結構有多復雜,數據類型有多豐富,到了展示的時候,全都一視同仁的成為字符串在頁面上展現出來。
2. 數據在Java世界中可以表現為豐富的數據結構和數據類型,你可以自行定義你喜歡的類,在類與類之間進行繼承、嵌套。我們通常會把這種模型稱之為復雜的對象樹。
此時,如果數據在頁面和Java世界中互相流轉傳遞,就會顯得不匹配。所以也就引出了幾個需要解決的問題:
1. 當數據從View層傳遞到Controller層時,我們應該保證一個扁平而分散在各處的數據集合能以一定的規則設置到Java世界中的對象樹中去。同時,能夠聰明的進行由字符串類型到Java中各個類型的轉化。
2. 當數據從Controller層傳遞到View層時,我們應該保證在View層能夠以某些簡易的規則對對象樹進行訪問。同時,在一定程度上控制對象樹中的數據的顯示格式。
如果我們稍微深入一些來思考這個問題,我們就會發現,解決數據由于表現形式的不同而發生流轉不匹配的問題對我們來說其實并不陌生。同樣的問題會發生在Java世界與數據庫世界中,面對這種對象與關系模型的不匹配,我們采用的解決方法是使用ORM框架,例如Hibernate,iBatis等等。那么現在,在Web層同樣也發生了不匹配,所以我們也需要使用一些工具來幫助我們解決問題。
在這里,我們主要討論的,是數據從View層傳遞到Controller層時的解決方案,而數據從Controller層傳遞到View層的解決方案,我們將在Struts2的Result章節重點討論。
OGNL —— 完美的催化劑
為了解決數據從View層傳遞到Controller層時的不匹配性,Struts2采納了XWork的OGNL方案。并且在OGNL的基礎上,構建了OGNLValueStack的機制,從而比較完美的解決了數據流轉中的不匹配性。
OGNL(Object Graph Navigation Language),是一種表達式語言。使用這種表達式語言,你可以通過某種表達式語法,存取Java對象樹中的任意屬性、調用Java對象樹的方法、同時能夠自動實現必要的類型轉化。如果我們把表達式看做是一個帶有語義的字符串,那么OGNL無疑成為了這個語義字符串與Java對象之間溝通的橋梁。
如何使用OGNL
讓我們先研究一下OGNL的API,他來自于Ognl的靜態方法:
我們可以看到,OGNL的API其實相當簡單,你可以通過傳遞三個參數來實現OGNL的一切操作。而這三個參數,被我稱為OGNL的三要素。
那么運用這個API,我們能干點什么呢?跑個測試看看結果:
我們可以看到,簡單的API,就已經能夠完成對各種對象樹的讀取和設值工作了。這也體現出OGNL的學習成本非常低。
在上面的測試用例中,需要特別強調進行區分的,是在針對不同內容進行取值或者設值時,OGNL表達式的不同。
上面這段內容摘自Struts2的Reference,我把這段話總結為以下2條規則:
A) 針對根對象(Root Object)的操作,表達式是自根對象到被訪問對象的某個鏈式操作的字符串表示。
B) 針對上下文環境(Context)的操作,表達式是自上下文環境(Context)到被訪問對象的某個鏈式操作的字符串表示,但是必須在這個字符串的前面加上#符號,以表示與訪問根對象的區別。
上面的這點區別咋看起來非常容易理解,不過一旦放到特定的環境中,就會顯示出其重要性,它可以解釋很多Struts2在頁面展示上取值的各種復雜的表達式的現象。這一點在下一篇文章中會進行具體的分析。
OGNL三要素
我把傳入OGNL的API的三個參數,稱之為OGNL的三要素。OGNL的操作實際上就是圍繞著這三個參數而進行的。
1. 表達式(Expression)
表達式是整個OGNL的核心,所有的OGNL操作都是針對表達式的解析后進行的。表達式會規定此次OGNL操作到底要干什么。
我們可以看到,在上面的測試中,name、department.name等都是表達式,表示取name或者department中的name的值。OGNL支持很多類型的表達式,之后我們會看到更多。
2. 根對象(Root Object)
根對象可以理解為OGNL的操作對象。在表達式規定了“干什么”以后,你還需要指定到底“對誰干”。
在上面的測試代碼中,user就是根對象。這就意味著,我們需要對user這個對象去取name這個屬性的值(對user這個對象去設置其中的department中的name屬性值)。
3. 上下文環境(Context)
有了表達式和根對象,我們實際上已經可以使用OGNL的基本功能。例如,根據表達式對根對象進行取值或者設值工作。
不過實際上,在OGNL的內部,所有的操作都會在一個特定的環境中運行,這個環境就是OGNL的上下文環境(Context)。說得再明白一些,就是這個上下文環境(Context),將規定OGNL的操作“在哪里干”。
OGNL的上下文環境是一個Map結構,稱之為OgnlContext。上面我們提到的根對象(Root Object),事實上也會被加入到上下文環境中去,并且這將作為一個特殊的變量進行處理,具體就表現為針對根對象(Root Object)的存取操作的表達式是不需要增加#符號進行區分的。
OgnlContext不僅提供了OGNL的運行環境。在這其中,我們還能設置一些自定義的parameter到Context中,以便我們在進行OGNL操作的時候能夠方便的使用這些parameter。不過正如我們上面反復強調的,我們在訪問這些parameter時,需要使用#作為前綴才能進行。
OGNL與模板
我們在嘗試了OGNL的基本操作并了解了OGNL的三要素之后,或許很容易把OGNL的操作與模板聯系起來進行比較。在很多方面,他們也的確有著相似之處。
對于模板,會有一些普通的輸出元素,也有一些模板語言特殊的符號構成的元素,這些元素一旦與具體的Java對象融合起來,就會得到我們需要的輸出結果。
而OGNL看起來也是非常的類似,OGNL中的表達式就雷同于模板語言的特殊符號,目的是針對某些Java對象進行存取。而OGNL與模板都將數據與展現分開,將數據放到某個特定的地方,具體來說,就是Java對象。只是OGNL與模板的語法結構不完全相同而已。
深入淺出OGNL
在了解了OGNL的API和基本操作以后,我們來深入到OGNL的內部來看看,挖掘一些更加深入的知識。
OGNL表達式
OGNL支持各種紛繁復雜的表達式。但是最最基本的表達式的原型,是將對象的引用值用點串聯起來,從左到右,每一次表達式計算返回的結果成為當前對象,后面部分接著在當前對象上進行計算,一直到全部表達式計算完成,返回最后得到的對象。OGNL則針對這條基本原則進行不斷的擴充,從而使之支持對象樹、數組、容器的訪問,甚至是類似SQL中的投影選擇等操作。
接下來我們就來看看一些常用的OGNL表達式:
1. 基本對象樹的訪問
對象樹的訪問就是通過使用點號將對象的引用串聯起來進行。
例如: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. 容器、數組、對象
OGNL支持對數組和ArrayList等容器的順序訪問:
例如:group.users[0]
同時,OGNL支持對Map的按鍵值查找:
例如:#session['mySessionPropKey']
不僅如此,OGNL還支持容器的構造的表達式:
例如:{"green", "red", "blue"}構造一個List,#{"key1" : "value1", "key2" : "value2", "key3" : "value3"}構造一個Map
你也可以通過任意類對象的構造函數進行對象新建:
例如:new java.net.URL("http://localhost/")
5. 對靜態方法或變量的訪問
要引用類的靜態方法和字段,他們的表達方式是一樣的@class@member或者@class@method(args):
例如:@com.javaeye.core.Resource@ENABLE,@com.javaeye.core.Resource@getAllResources
6. 方法調用
直接通過類似Java的方法調用方式進行,你甚至可以傳遞參數:
例如:user.getName(),group.users.size(),group.containsUser(#requestUser)
7. 投影和選擇
OGNL支持類似數據庫中的投影(projection) 和選擇(selection)。
投影就是選出集合中每個元素的相同屬性組成新的集合,類似于關系數據庫的字段操作。投影操作語法為 collection.{XXX},其中XXX 是這個集合中每個元素的公共屬性。
例如:group.userList.{username}將獲得某個group中的所有user的name的列表。
選擇就是過濾滿足selection 條件的集合元素,類似于關系數據庫的紀錄操作。選擇操作的語法為: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教程以及一些網絡上的一些文章,特此列出:
http://www.lifevv.com/java/doc/20071018173750030.html
http://blog.csdn.net/ice_fire2008/archive/2008/05/12/2438817.aspx
OGNLContext
OGNLContext就是OGNL的運行上下文環境。OGNLContext其實是一個Map結構,如果查看一下它的源碼,就會發現,它其實實現了java.utils.Map的接口。當你在調用OGNL的取值或者設值的方法時,你可能會自己定義一個Context,并且將它傳遞給方法。事實上,你所傳遞進去的這個Context,會在OGNL內部被轉化成OGNLContext,而你傳遞進去的所有的鍵值對,也會被OGNLContext接管維護,這里有點類似一個裝飾器,向你屏蔽了一些其內部的實現機理。
在OGNLContext的內部維護的東西很多,其中,我挑選2個比較重要的提一下。一個是你在調用方法時傳入的Context,它會被維護在OGNL內部,并且作為存取變量的基礎依據。另外一個,是在Context內部維護了一個key為root的值,它將規定在OGNLContext進行計算時,哪個元素被指定為根對象。其在進行存取時,將會被特殊對待。
this指針
我們知道,OGNL表達式是以點進行串聯的一個字符串鏈式表達式。而這個表達式在進行計算的時候,從左到右,每一次表達式計算返回的結果成為當前對象,并繼續進行計算,直到得到計算結果。每次計算的中間對象都會放在一個叫做this的變量里面這個this變量就稱之為this指針。
例如:group.userList.size().(#this+1).toString()
在這個例子中,#this其實就是group.userList.size()的計算結構。
使用this指針,我們就可以在OGNL表達式中進行一些簡單的計算,從而完成我們的計算邏輯,而this指針在lamba表達式的引用中尤為廣泛,有興趣的讀者可以深入研究OGNL自帶的文檔中lamba表達式的章節。
默認行為和類型轉化
在我們所講述的所有的OGNL的操作中,實際上,全部都忽略了OGNL內部幫助你完成的很多默認行為和類型轉化方面的工作。
我們來看一下OGNL在進行操作初始化時候的一個函數簽名:
可以看到,在初始化時,OGNL還需要額外初始化一個類型轉化的接口和一些其他的信息。只不過這些默認行為,由OGNL的內部屏蔽了。
一旦需要自己定義針對某個特定類型的類型轉化方式,你就需要實現TypeConverter接口,并且在OGNL中進行注冊。
同時,如果需要對OGNL的許多默認行為做出改變,則需要通過設置OGNL的全局環境變量進行。
上述的這些內容,有些會在后面的章節涉及,有興趣的讀者,也可以參閱OGNL的源碼和OGNL的文檔尋求幫助。
首先讓我們花費1分鐘的時間來簡單思考一個問題,MVC這3者之間,到底是通過什么真正融合起來的?
有人說是Controller,因為它是核心控制器,沒有Controller,MVC就無從談起,失去了職責劃分的原本初衷。也有人說是View,因為所有的需求都是頁面驅動的,沒有頁面,就沒有請求,沒有請求,也談不上控制器和數據模型。
個人觀點:貫穿MVC模型之間起到粘合劑作用的是數據。數據在View層成為了展示的內容,而在Controller層,成為了操作的載體,所以數據是整個MVC的核心。
流轉的數據
無論MVC三者之間的粘合劑到底是什么,數據在各個層次之間進行流轉是一個不爭的事實。而這種流轉,也就會面臨一些困境,這些困境,是由于數據在不同世界中的表現形式不同而造成的:
1. 數據在頁面上是一個扁平的,不帶數據類型的字符串,無論你的數據結構有多復雜,數據類型有多豐富,到了展示的時候,全都一視同仁的成為字符串在頁面上展現出來。
2. 數據在Java世界中可以表現為豐富的數據結構和數據類型,你可以自行定義你喜歡的類,在類與類之間進行繼承、嵌套。我們通常會把這種模型稱之為復雜的對象樹。
此時,如果數據在頁面和Java世界中互相流轉傳遞,就會顯得不匹配。所以也就引出了幾個需要解決的問題:
1. 當數據從View層傳遞到Controller層時,我們應該保證一個扁平而分散在各處的數據集合能以一定的規則設置到Java世界中的對象樹中去。同時,能夠聰明的進行由字符串類型到Java中各個類型的轉化。
2. 當數據從Controller層傳遞到View層時,我們應該保證在View層能夠以某些簡易的規則對對象樹進行訪問。同時,在一定程度上控制對象樹中的數據的顯示格式。
如果我們稍微深入一些來思考這個問題,我們就會發現,解決數據由于表現形式的不同而發生流轉不匹配的問題對我們來說其實并不陌生。同樣的問題會發生在Java世界與數據庫世界中,面對這種對象與關系模型的不匹配,我們采用的解決方法是使用ORM框架,例如Hibernate,iBatis等等。那么現在,在Web層同樣也發生了不匹配,所以我們也需要使用一些工具來幫助我們解決問題。
在這里,我們主要討論的,是數據從View層傳遞到Controller層時的解決方案,而數據從Controller層傳遞到View層的解決方案,我們將在Struts2的Result章節重點討論。
OGNL —— 完美的催化劑
為了解決數據從View層傳遞到Controller層時的不匹配性,Struts2采納了XWork的OGNL方案。并且在OGNL的基礎上,構建了OGNLValueStack的機制,從而比較完美的解決了數據流轉中的不匹配性。
OGNL(Object Graph Navigation Language),是一種表達式語言。使用這種表達式語言,你可以通過某種表達式語法,存取Java對象樹中的任意屬性、調用Java對象樹的方法、同時能夠自動實現必要的類型轉化。如果我們把表達式看做是一個帶有語義的字符串,那么OGNL無疑成為了這個語義字符串與Java對象之間溝通的橋梁。
如何使用OGNL
讓我們先研究一下OGNL的API,他來自于Ognl的靜態方法:
- /**
- * Evaluates the given OGNL expression tree to extract a value from the given root
- * object. The default context is set for the given context and root via
- * <CODE>addDefaultContext()</CODE>.
- *
- * @param tree the OGNL expression tree to evaluate, as returned by parseExpression()
- * @param context the naming context for the evaluation
- * @param root the root object for the OGNL expression
- * @return the result of evaluating the expression
- * @throws MethodFailedException if the expression called a method which failed
- * @throws NoSuchPropertyException if the expression referred to a nonexistent property
- * @throws InappropriateExpressionException if the expression can't be used in this context
- * @throws OgnlException if there is a pathological environmental problem
- */
- public static Object getValue( Object tree, Map context, Object root ) throws OgnlException;
- /**
- * Evaluates the given OGNL expression tree to insert a value into the object graph
- * rooted at the given root object. The default context is set for the given
- * context and root via <CODE>addDefaultContext()</CODE>.
- *
- * @param tree the OGNL expression tree to evaluate, as returned by parseExpression()
- * @param context the naming context for the evaluation
- * @param root the root object for the OGNL expression
- * @param value the value to insert into the object graph
- * @throws MethodFailedException if the expression called a method which failed
- * @throws NoSuchPropertyException if the expression referred to a nonexistent property
- * @throws InappropriateExpressionException if the expression can't be used in this context
- * @throws OgnlException if there is a pathological environmental problem
- */
- public static void setValue( Object tree, Map context, Object root, Object value ) throws OgnlException
我們可以看到,OGNL的API其實相當簡單,你可以通過傳遞三個參數來實現OGNL的一切操作。而這三個參數,被我稱為OGNL的三要素。
那么運用這個API,我們能干點什么呢?跑個測試看看結果:
- /**
- * @author Downpour
- */
- public class User {
- private Integer id;
- private String name;
- private Department department = new Department();
- public User() {
- }
- // setter and getters
- }
- //=========================================================================
- /**
- * @author Downpour
- */
- public class Department {
- private Integer id;
- private String name;
- public Department() {
- }
- // setter and getters
- }
- //=========================================================================
- /**
- * @author Downpour
- */
- public class OGNLTestCase extends TestCase {
- /**
- *
- * @throws Exception
- */
- @SuppressWarnings("unchecked")
- @Test
- public void testGetValue() throws Exception {
- // Create root object
- User user = new User();
- user.setId(1);
- user.setName("downpour");
- // Create context
- Map context = new HashMap();
- context.put("introduction","My name is ");
- // Test to directly get value from root object, with no context
- Object name = Ognl.getValue(Ognl.parseExpression("name"), user);
- assertEquals("downpour",name);
- // Test to get value(parameter) from context
- Object contextValue = Ognl.getValue(Ognl.parseExpression("#introduction"), context, user);
- assertEquals("My name is ", contextValue);
- // Test to get value and parameter from root object and context
- Object hello = Ognl.getValue(Ognl.parseExpression("#introduction + name"), context, user);
- assertEquals("My name is downpour",hello);
- }
- /**
- *
- * @throws Exception
- */
- @SuppressWarnings("unchecked")
- @Test
- public void testSetValue() throws Exception {
- // Create root object
- User user = new User();
- user.setId(1);
- user.setName("downpour");
- // Set value according to the expression
- Ognl.setValue("department.name", user, "dev");
- assertEquals("dev", user.getDepartment().getName());
- }
- }
我們可以看到,簡單的API,就已經能夠完成對各種對象樹的讀取和設值工作了。這也體現出OGNL的學習成本非常低。
在上面的測試用例中,需要特別強調進行區分的,是在針對不同內容進行取值或者設值時,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 (#).
上面這段內容摘自Struts2的Reference,我把這段話總結為以下2條規則:
A) 針對根對象(Root Object)的操作,表達式是自根對象到被訪問對象的某個鏈式操作的字符串表示。
B) 針對上下文環境(Context)的操作,表達式是自上下文環境(Context)到被訪問對象的某個鏈式操作的字符串表示,但是必須在這個字符串的前面加上#符號,以表示與訪問根對象的區別。
上面的這點區別咋看起來非常容易理解,不過一旦放到特定的環境中,就會顯示出其重要性,它可以解釋很多Struts2在頁面展示上取值的各種復雜的表達式的現象。這一點在下一篇文章中會進行具體的分析。
OGNL三要素
我把傳入OGNL的API的三個參數,稱之為OGNL的三要素。OGNL的操作實際上就是圍繞著這三個參數而進行的。
1. 表達式(Expression)
表達式是整個OGNL的核心,所有的OGNL操作都是針對表達式的解析后進行的。表達式會規定此次OGNL操作到底要干什么。
我們可以看到,在上面的測試中,name、department.name等都是表達式,表示取name或者department中的name的值。OGNL支持很多類型的表達式,之后我們會看到更多。
2. 根對象(Root Object)
根對象可以理解為OGNL的操作對象。在表達式規定了“干什么”以后,你還需要指定到底“對誰干”。
在上面的測試代碼中,user就是根對象。這就意味著,我們需要對user這個對象去取name這個屬性的值(對user這個對象去設置其中的department中的name屬性值)。
3. 上下文環境(Context)
有了表達式和根對象,我們實際上已經可以使用OGNL的基本功能。例如,根據表達式對根對象進行取值或者設值工作。
不過實際上,在OGNL的內部,所有的操作都會在一個特定的環境中運行,這個環境就是OGNL的上下文環境(Context)。說得再明白一些,就是這個上下文環境(Context),將規定OGNL的操作“在哪里干”。
OGNL的上下文環境是一個Map結構,稱之為OgnlContext。上面我們提到的根對象(Root Object),事實上也會被加入到上下文環境中去,并且這將作為一個特殊的變量進行處理,具體就表現為針對根對象(Root Object)的存取操作的表達式是不需要增加#符號進行區分的。
OgnlContext不僅提供了OGNL的運行環境。在這其中,我們還能設置一些自定義的parameter到Context中,以便我們在進行OGNL操作的時候能夠方便的使用這些parameter。不過正如我們上面反復強調的,我們在訪問這些parameter時,需要使用#作為前綴才能進行。
OGNL與模板
我們在嘗試了OGNL的基本操作并了解了OGNL的三要素之后,或許很容易把OGNL的操作與模板聯系起來進行比較。在很多方面,他們也的確有著相似之處。
對于模板,會有一些普通的輸出元素,也有一些模板語言特殊的符號構成的元素,這些元素一旦與具體的Java對象融合起來,就會得到我們需要的輸出結果。
而OGNL看起來也是非常的類似,OGNL中的表達式就雷同于模板語言的特殊符號,目的是針對某些Java對象進行存取。而OGNL與模板都將數據與展現分開,將數據放到某個特定的地方,具體來說,就是Java對象。只是OGNL與模板的語法結構不完全相同而已。
深入淺出OGNL
在了解了OGNL的API和基本操作以后,我們來深入到OGNL的內部來看看,挖掘一些更加深入的知識。
OGNL表達式
OGNL支持各種紛繁復雜的表達式。但是最最基本的表達式的原型,是將對象的引用值用點串聯起來,從左到右,每一次表達式計算返回的結果成為當前對象,后面部分接著在當前對象上進行計算,一直到全部表達式計算完成,返回最后得到的對象。OGNL則針對這條基本原則進行不斷的擴充,從而使之支持對象樹、數組、容器的訪問,甚至是類似SQL中的投影選擇等操作。
接下來我們就來看看一些常用的OGNL表達式:
1. 基本對象樹的訪問
對象樹的訪問就是通過使用點號將對象的引用串聯起來進行。
例如: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. 容器、數組、對象
OGNL支持對數組和ArrayList等容器的順序訪問:
例如:group.users[0]
同時,OGNL支持對Map的按鍵值查找:
例如:#session['mySessionPropKey']
不僅如此,OGNL還支持容器的構造的表達式:
例如:{"green", "red", "blue"}構造一個List,#{"key1" : "value1", "key2" : "value2", "key3" : "value3"}構造一個Map
你也可以通過任意類對象的構造函數進行對象新建:
例如:new java.net.URL("http://localhost/")
5. 對靜態方法或變量的訪問
要引用類的靜態方法和字段,他們的表達方式是一樣的@class@member或者@class@method(args):
例如:@com.javaeye.core.Resource@ENABLE,@com.javaeye.core.Resource@getAllResources
6. 方法調用
直接通過類似Java的方法調用方式進行,你甚至可以傳遞參數:
例如:user.getName(),group.users.size(),group.containsUser(#requestUser)
7. 投影和選擇
OGNL支持類似數據庫中的投影(projection) 和選擇(selection)。
投影就是選出集合中每個元素的相同屬性組成新的集合,類似于關系數據庫的字段操作。投影操作語法為 collection.{XXX},其中XXX 是這個集合中每個元素的公共屬性。
例如:group.userList.{username}將獲得某個group中的所有user的name的列表。
選擇就是過濾滿足selection 條件的集合元素,類似于關系數據庫的紀錄操作。選擇操作的語法為: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教程以及一些網絡上的一些文章,特此列出:
http://www.lifevv.com/java/doc/20071018173750030.html
http://blog.csdn.net/ice_fire2008/archive/2008/05/12/2438817.aspx
OGNLContext
OGNLContext就是OGNL的運行上下文環境。OGNLContext其實是一個Map結構,如果查看一下它的源碼,就會發現,它其實實現了java.utils.Map的接口。當你在調用OGNL的取值或者設值的方法時,你可能會自己定義一個Context,并且將它傳遞給方法。事實上,你所傳遞進去的這個Context,會在OGNL內部被轉化成OGNLContext,而你傳遞進去的所有的鍵值對,也會被OGNLContext接管維護,這里有點類似一個裝飾器,向你屏蔽了一些其內部的實現機理。
在OGNLContext的內部維護的東西很多,其中,我挑選2個比較重要的提一下。一個是你在調用方法時傳入的Context,它會被維護在OGNL內部,并且作為存取變量的基礎依據。另外一個,是在Context內部維護了一個key為root的值,它將規定在OGNLContext進行計算時,哪個元素被指定為根對象。其在進行存取時,將會被特殊對待。
this指針
我們知道,OGNL表達式是以點進行串聯的一個字符串鏈式表達式。而這個表達式在進行計算的時候,從左到右,每一次表達式計算返回的結果成為當前對象,并繼續進行計算,直到得到計算結果。每次計算的中間對象都會放在一個叫做this的變量里面這個this變量就稱之為this指針。
例如:group.userList.size().(#this+1).toString()
在這個例子中,#this其實就是group.userList.size()的計算結構。
使用this指針,我們就可以在OGNL表達式中進行一些簡單的計算,從而完成我們的計算邏輯,而this指針在lamba表達式的引用中尤為廣泛,有興趣的讀者可以深入研究OGNL自帶的文檔中lamba表達式的章節。
默認行為和類型轉化
在我們所講述的所有的OGNL的操作中,實際上,全部都忽略了OGNL內部幫助你完成的很多默認行為和類型轉化方面的工作。
我們來看一下OGNL在進行操作初始化時候的一個函數簽名:
- /**
- * Appends the standard naming context for evaluating an OGNL expression
- * into the context given so that cached maps can be used as a context.
- *
- * @param root the root of the object graph
- * @param context the context to which OGNL context will be added.
- * @return Context Map with the keys <CODE>root</CODE> and <CODE>context</CODE>
- * set appropriately
- */
- public static Map addDefaultContext( Object root, ClassResolver classResolver, TypeConverter converter, MemberAccess memberAccess, Map context );
可以看到,在初始化時,OGNL還需要額外初始化一個類型轉化的接口和一些其他的信息。只不過這些默認行為,由OGNL的內部屏蔽了。
一旦需要自己定義針對某個特定類型的類型轉化方式,你就需要實現TypeConverter接口,并且在OGNL中進行注冊。
同時,如果需要對OGNL的許多默認行為做出改變,則需要通過設置OGNL的全局環境變量進行。
上述的這些內容,有些會在后面的章節涉及,有興趣的讀者,也可以參閱OGNL的源碼和OGNL的文檔尋求幫助。