struts2.0與ognl淺析

struts2.0與ognl淺析(轉載)

(2010-02-24 11:34:17)
標籤:

struts2.0

ognl

雜談

分類: java

OGNL —— 完美的催化劑

         爲了解決數據從View層傳遞到Controller層時的不匹配性,Struts2採納了XWork的OGNL方案。並且在OGNL的基礎上,構建了OGNLValueStack的機制 ,從而比較完美的解決了數據流轉中的不匹配性。

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

如何使用OGNL

讓我們先研究一下OGNL的API,他來自於Ognl的靜態方法:

Java代碼 複製代碼
  1.   
  2.   
  3. public   static  Object getValueObject tree, Map context, Object root  )  throws  OgnlException;   
  4.   
  5.   
  6. public   static   void  setValueObject tree, Map context, Object root, Object value  )  throws  OgnlException  

 

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

 

簡單的API,就已經能夠完成對各種對象樹的讀取和設值工作了。這也體現出OGNL的學習成本非常低。

在上面的測試用例中,需要特別強調進行區分的,是在針對不同內容進行取值或者設值時,OGNL表達式的不同。

Struts2 Reference 寫道
The framework uses a standard naming context to evaluate OGNL expression_r_rs. 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_r_r, 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_r_r)

      表達式是整個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自帶的文檔:


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在進行操作初始化時候的一個函數簽名:

 

  1.   
  2. public   static  Map addDefaultContext( Object root, ClassResolver classResolver, TypeConverter converter, MemberAccess memberAccess, Map context );  

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

一旦需要自己定義針對某個特定類型的類型轉化方式,你就需要實現TypeConverter接口,並且在OGNL中進行註冊。

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

OGNL是XWork引入的一個非常有效的數據處理的工具。我們已經瞭解了OGNL的基本操作和OGNL的內部結構,接下來,我們來看看XWork對OGNL做了什麼樣的加強,以及OGNL的體系在Struts2中如何運轉。

  1.   
  2. public   class  User {   
  3.        
  4.      private  Integer id;   
  5.        
  6.      private  String name;   
  7.        
  8.      private  Department department =  new  Department();   
  9.        
  10.          // setter and getters   
  11. }   
  12.   
  13. //=========================================================================   
  14.   
  15.   
  16. public   class  Department {   
  17.        
  18.      private  Integer id;   
  19.        
  20.      private  String name;   
  21.            
  22.          // setter and getters   
  23. }   
  24.   
  25. //=========================================================================   
  26.   
  27. <form method= "post"  action= "/struts-example/ognl.action" >   
  28.     user name: <input type= "text"  name= "user.name"  value= "downpour"  />   
  29.     department name: <input type= "text"  name= "department.name"  value= "dev"  />   
  30.     <input type= "submit"  value= "submit"  />   
  31. </form>   
  32.   
  33. //=========================================================================   
  34.   
  35.   
  36. public   class  OgnlAction  extends  ActionSupport {   
  37.   
  38.      private   static   final  Log logger = LogFactory.getLog(OgnlAction. class );   
  39.   
  40.      private  User user;   
  41.        
  42.      private  Department department;   
  43.        
  44.        
  45.      @Override   
  46.      public  String execute()  throws  Exception {   
  47.         logger.info( "user name:"  + user.getName());    // -> downpour   
  48.         logger.info( "department name:"  + department.getName());    // -> dev   
  49.          return   super .execute();   
  50.     }   
  51.   
  52.      // setter and getters   
  53. }   
  54.   
  55. //=========================================================================   
  56.   
  57. user name: <s:property value= "user.name"  />   
  58. department name: <s:property value= "department.name"  />   
  59.   
  60. //=========================================================================   
我們可以看到在JSP中,form中的元素input等,都使用OGNL的表達式作爲name的值。而在form提交時,這些值都會被設置到Action 中的Java對象中。而當Action轉向到JSP時,Struts2的Tag又可以從Action的Java對象中,通過OGNL進行取值。

在這裏,你看不到任何的OGNL的代碼級別操作,因爲這些都在Struts2內部進行了封裝。而這些封裝,都是建立在OGNL的基本概念,也就是根對象和上下文環境之上。下面就分別就這兩個方面分別進行講解。

ValueStack —— 對OGNL的加強

細心的讀者可能會發現,在上面的例子中,我們使用了不同的表達式,針對Action中的不同的Java對象進行設值。再結合上一講我們所例舉的OGNL的 代碼操作示例,我們有強烈的理由懷疑,Struts2在內部有可能執行了這樣的操作,才使得頁面到Action的設值工作順利完成:
  1. // "user.name" as OGNL expression_r_r, action as OGNL Root object   
  2. Ognl.setValue( "user.name" , action,  "downpour" );   
  3. Ognl.setValue( "department.name" , action,  "dev" );  

如果這個懷疑是正確的,那麼我們就能得出這樣一個結論:Struts2的Action是OGNL操作的根對象。

這個結論是我們從現象上推出來的,至於它到底正確與否,我們之後可以通過源碼分析來進行驗證,在這裏先賣一個關子,姑且認爲它是正確的。不過這個結論對我 們來說非常重要,因爲這個結論Struts2的Tag,JSTL和Freemarker等表示層元素獲取Action中變量的值打下了堅實的基礎。

在Struts2(XWork)中,不僅把Action作爲OGNL操作的根對象,作爲對OGNL的擴展,它還引入了一個ValueStack 的概念。這個概念代表了什麼呢?還是讓我們看看Struts2的Reference怎麼說:

Struts2 Reference 寫道
The biggest addition that XWork provides on top of OGNL is the support for the ValueStack. While OGNL operates under the assumption there is only one "root", XWork's ValueStack concept requires there be many "roots".


很明顯,ValueStack依照它的結構和作用,至少爲我們提供兩大特性:

1. ValueStack是一個堆棧結構,堆棧中的每個元素對於OGNL操作來說,都被看作是根對象。

2. 由於ValueStack是一個堆棧結構,所以其中的元素都是有序的,對於某個OGNL表達式來說,OGNL將自堆棧頂部開始查找,並返回第一個符合條件的對象元素。


這裏我們有必要對第二點囉嗦幾句,舉個具體的例子來說(這個例子同樣摘自Struts2的Reference):如果在ValueStack中有2個對 象,分別是“動物”和“人”,這兩個對象都具備一個屬性,叫做name,而“動物”還有一個屬性叫species,“人”還有個屬性叫salary。其 中,“動物”對象在ValueStack的棧頂,而“人”這個對象在棧底。那麼看看下面的OGNL表達式將返回什麼?

  1. species     // call to animal.getSpecies()   
  2. salary      // call to person.getSalary()   
  3. name        // call to animal.getName() because animal is on the top   

對於name這個屬性,返回的將是“動物”的name,因爲“動物”在棧頂,會被先匹配到OGNL的表達式。但是有的時候,你可能需要訪問“人”的name屬性,怎麼辦呢?你可以通過下面的方法:

  1. [ 0 ].name    // call to animal.getName()   
  2. [ 1 ].name    // call to person.getName()   

Struts2中的OGNL上下文環境

有了ValueStack,我們再來仔細研究一下Struts2中OGNL的上下文環境。

Struts2 Reference 寫道
The framework sets the OGNL context to be our ActionContext, and the value stack to be the OGNL root object. (The value stack is a set of several objects, but to OGNL it appears to be a single object.) Along with the value stack, the framework places other objects in the ActionContext, including Maps representing the application, session, and request contexts. These objects coexist in the ActionContext, alongside the value stack (our OGNL root)



也就是說,ActionContext是Struts2中OGNL的上下文環境。它維護着一個Map的結構,下面是這個結構的圖示:



其中,ValueStack是這個上下文環境中的根對象,而除了這個根對象以外,Struts2還在這個上下文環境中放了許多額外的變量,而這些變量多數 都是被XWork封裝過的Servlet對象,例如request,session,servletContext(application)等,這些對 象都被封裝成Map對象,隨着ActionContext作用於整個Action執行的生命週期中。

在這裏,或許有些讀者會提出問題來,爲什麼好好的Servlet對象要在這裏被封裝成Map對象呢?我想原因可能有以下兩個:

1. 對Struts2的Action徹底屏蔽Servlet容器,從而無需再使用底層Servlet API進行編程。你所面對的,將永遠是一個又一個的Java對象。

2. 便於各種View技術,例如JSP,Freemarker,Velocity等對ValueStack中上下文環境,尤其是Servlet對象中的數據進 行讀取。試想,如果在這裏不將HttpServletRequest,HttpSession等Servlet對象轉化成Map,那麼我們將很難通過 OGNL表達式,對這些Servlet對象中的值進行讀取。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章