1. 數據在頁面上是一個扁平的,不帶數據類型的字符串,無論你的數據結構有多複雜,數據類型有多豐富,到了展示的時候,全都一視同仁的成爲字符串在頁面上展現出來。
2. 數據在Java世界中可以表現爲豐富的數據結構和數據類型,你可以自行定義你喜歡的類,在類與類之間進行繼承、嵌套。我們通常會把這種模型稱之爲複雜的對象樹。
此時,如果數據在頁面和Java世界中互相流轉傳遞,就會顯得不匹配。所以也就引出了幾個需要解決的問題:
1. 當數據從View層傳遞到Controller層時,我們應該保證一個扁平而分散在各處的數據集合能以一定的規則設置到Java世界中的對象樹中去。同時,能夠聰明的進行由字符串類型到Java中各個類型的轉化。
2. 當數據從Controller層傳遞到View層時,我們應該保證在View層能夠以某些簡易的規則對對象樹進行訪問。同時,在一定程度上控制對象樹中的數據的顯示格式。
如果我們稍微深入一些來思考這個問題,我們就會發現,解決數據由於表現形式的不同而發生流轉不匹配的問題對我們來說其實並不陌生。同樣的問題會發生在Java世界與數據庫世界中,面對這種對象與關係模型的不匹配,我們採用的解決方法是使用ORM框架,例如Hibernate,iBatis等等。那麼現在,在Web層同樣也發生了不匹配,所以我們也需要使用一些工具來幫助我們解決問題。爲了解決數據從View層傳遞到Controller層時的不匹配性,Struts2採納了XWork的一套完美方案。並且在此的基礎上,構建了一個完美的機制,從而比較完美的解決了數據流轉中的不匹配性。相信大家看到這一定猜出來了這裏的完美方案和完美機制了。對,這就是OGNL方案和OGNLValueStack機制
基本概念
OGNL(Object Graph Navigation Language),是一種表達式語言。使用這種表達式語言,你可以通過某種表達式語法,存取Java對象樹中的任意屬性、調用Java對象樹的方法、同時能夠自動實現必要的類型轉化。如果我們把表達式看做是一個帶有語義的字符串,那麼OGNL無疑成爲了這個語義字符串與Java對象之間溝通的橋樑。既然OGNL那麼強大,那麼讓我們一起來研究一下他的API,看看如何使用OGNL.
OGNL的API看起來就是兩個簡單的靜態方法:
- public static Object getValue( Object tree, Map context, Object root ) throws OgnlException;
- public static void setValue( Object tree, Map context, Object root, Object value ) throws OgnlException
- public static Object getValue( Object tree, Map context, Object root ) throws OgnlException;
- public static void setValue( Object tree, Map context, Object root, Object value ) throws OgnlException
我們可以看到,簡單的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 (#).
針對上面的話,我們可以簡單的理解爲下面兩點:
A) 針對根對象(Root Object)的操作,表達式是自根對象到被訪問對象的某個鏈式操作的字符串表示。
B) 針對上下文環境(Context)的操作,表達式是自上下文環境(Context)到被訪問對象的某個鏈式操作的字符串表示,但是必須在這個字符串的前面加上#符號,以表示與訪問根對象的區別。
OGNL三要素:
很多人習慣上把傳入OGNL的API的三個參數,稱之爲OGNL的三要素。OGNL的操作實際上就是圍繞着這三個參數而進行的。
看下面一段測試代碼:
- Ognl.setValue("department.name", user2, "dev");
- System.out.println(user2.getDepartment().getName());
- Ognl.setValue(Ognl.parseexpression_r("department.name"), context, user2, "otherDev");
- System.out.println(user2.getDepartment().getName());
- Ognl.setValue("department.name", user2, "dev");
- System.out.println(user2.getDepartment().getName());
- Ognl.setValue(Ognl.parseexpression_r("department.name"), context, user2, "otherDev");
- System.out.println(user2.getDepartment().getName());
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表達式實現原理
Struts 2中的OGNL Context實現者爲ActionContext,它結構示意圖如下:
當Struts2接受一個請求時,會迅速創建ActionContext,ValueStack,action 。然後把action存放進ValueStack,所以action的實例變量可以被OGNL訪問。訪問上下文(Context)中的對象需要使用#符號標註命名空間,如#application、#session,另外OGNL會設定一個根對象(root對象),在Struts2中根對象就是ValueStack(值棧) 。如果要訪問根對象(即ValueStack)中對象的屬性,則可以省略#命名空間,直接訪問該對象的屬性即可。
在struts2中,根對象ValueStack的實現類爲OgnlValueStack,該對象不是我們想像的只存放單個值,而是存放一組對象。在OgnlValueStack類裏有一個List類型的root變量,就是使用他存放一組對象
|--request |--application context ------|--OgnlValueStack root變量[action, OgnlUtil, ... ] |--session |--attr |--parameters,在root變量中處於第一位的對象叫棧頂對象。通常我們在OGNL表達式裏直接寫上屬性的名稱即可訪問root變量裏對象的屬性,搜索順序是從棧頂對象開始尋找,如果棧頂對象不存在該屬性,就會從第二個對象尋找,如果沒有找到就從第三個對象尋找,依次往下訪問,直到找到爲止。
大家注意: Struts2中,OGNL表達式需要配合Struts標籤纔可以使用。如:<s:property
value="name"/>
由於ValueStack(值棧)是Struts 2中OGNL的根對象,如果用戶需要訪問值棧中的對象,在JSP頁面可以直接通過下面的EL表達式訪問ValueStack(值棧)中對象的屬性:
${foo} //獲得值棧中某個對象的foo屬性。如果訪問其他Context中的對象,由於他們不是根對象,所以在訪問時,需要添加#前綴。
application對象:用於訪問ServletContext,例如#application.userName或者#application['userName'],相當於調用ServletContext的getAttribute("username")。
session對象:用來訪問HttpSession,例如#session.userName或者#session['userName'],相當於調用session.getAttribute("userName")。
request對象:用來訪問HttpServletRequest屬性(attribute)的Map,例如#request.userName或者#request['userName'],相當於調用request.getAttribute("userName")。
parameters對象:用於訪問HTTP的請求參數,例如#parameters.userName或者#parameters['userName'],相當於調用request.getParameter("username")。
attr對象:用於按page->request->session->application順序訪問其屬性。
好了,基本概念和原理佔時先介紹到這,下篇博客則着重說一下OGNL表達式的基本語法和用法,謝謝大家一直以來的支持。
在上篇博客,我們一起看了《ognl概念和原理詳解》,我們大約的知道了ognl的基本實現原理和一些基本概念,這節我們一起來學習一下OGNL表達式的基本語法和基本用法,首先我們一起來看一下OGNL中的#、%和$符號。
一.OGNL中的#、%和$符號
#、%和$符號在OGNL表達式中經常出現,而這三種符號也是開發者不容易掌握和理解的部分。在這裏我們簡單介紹它們的相應用途。
1.#符號的三種用法
1)訪問非根對象屬性,例如示例中的#session.msg表達式,由於Struts 2中值棧被視爲根對象,所以訪問其他非根對象時,需要加#前綴。實際上,#相當於ActionContext. getContext();#session.msg表達式相當於ActionContext.getContext().getSession(). getAttribute("msg") 。
2)用於過濾和投影(projecting)集合,如示例中的persons.{?#this.age>20}。
3) 用來構造Map,例如示例中的#{'foo1':'bar1', 'foo2':'bar2'}。
2.%符號
%符號的用途是在標誌的屬性爲字符串類型時,計算OGNL表達式的值。如下面的代碼所示:
<h3>構造Map</h3>
<s:set name="foobar" value="#{'foo1':'bar1', 'foo2':'bar2'}" />
<p>The value of key "foo1" is <s:property value="#foobar['foo1']" /></p>
<p>不使用%:<s:url value="#foobar['foo1']" /></p>
<p>使用%:<s:url value="%{#foobar['foo1']}" /></p>
運行界面如下所示。
he value of key "foo1" is bar1
不使用%:#foobar['foo1']
使用%:bar1
3.$符號
$符號主要有兩個方面的用途。
1) 在國際化資源文件中,引用OGNL表達式,例如國際化資源文件中的代碼:reg.agerange=國際化資源信息:年齡必須在${min}同${max}之間。
2) 在Struts 2框架的配置文件中引用OGNL表達式,例如下面的代碼片斷所示:
- <validators>
- <field name="intb">
- <field-validator type="int">
- <param name="min">10</param>
- <param name="max">100</param>
- <message>BAction-test校驗:數字必須爲${min}爲${max}之間!</message>
- </field-validator>
- </field>
- </validators>
- <validators>
- <field name="intb">
- <field-validator type="int">
- <param name="min">10</param>
- <param name="max">100</param>
- <message>BAction-test校驗:數字必須爲${min}爲${max}之間!</message>
- </field-validator>
- </field>
- </validators>
二.我們一起看一下OGNL常用表達式:
1. 當使用OGNL調用靜態方法的時候,需要按照如下語法編寫表達式:
@package.classname@methodname(parameter)
2. 對於OGNL來說,java.lang.Math是其的默認類,如果調用java.lang.Math的靜態方法時,無需指定類的名字,比如:@@min(4, 10);
3. 對於OGNL來說,數組與集合是一樣的,都是通過下標索引來去訪問的。
獲取List:<s:property value="testList"/><br> 獲取List中的某一個元素(可以使用類似於數組中的下標獲取List中的內容): <s:property value="testList[0]"/><br> 獲取Set:<s:property value="testSet"/><br> 獲取Set中的某一個元素(Set由於沒有順序,所以不能使用下標獲取數據): <s:property value="testSet[0]"/><br> × 獲取Map:<s:property value="testMap"/><br> 獲取Map中所有的鍵:<s:property value="testMap.keys"/><br> 獲取Map中所有的值:<s:property value="testMap.values"/><br> 獲取Map中的某一個元素(可以使用類似於數組中的下標獲取List中的內容): <s:property value="testMap['m1']"/><br> 獲取List的大小:<s:property value="testSet.size"/><br> |
4. 使用OGNL來處理映射(Map)的語法格式如下所示:
#{‘key1’: ‘value1’, ‘key2’: ‘value2’, ‘key3’: ‘value3’};
5. 過濾(filtering):collection.{? expression}
6. OGNL針對集合提供了一些僞屬性(如size,isEmpty),讓我們可以通過屬性的方式來調用方法(本質原因在於集合當中的很多方法並不符合JavaBean的命名規則),但我麼你依然還可以通過調用方法來實現與僞屬性相同的目的。
7. 過濾(filtering),獲取到集合中的第一個元素:collection.{^ expression}
8. 過濾(filtering),獲取到集合中的最後一個元素:collection.{& expression}
9. 在使用過濾操作時,我們通常都會使用#this,該表達式用於代表當前正在迭代的集合中的對象(聯想增強的for循環)
10. 投影(projection):collection.{expression}
11. 過濾與投影之間的差別:類比於數據庫中的表,過濾是取行的操作,而投影是取列的操作。 具體舉例如下:
利用選擇獲取List中成績及格的對象:<s:property value="stus.{?#this.grade>=60}"/><br> 利用選擇獲取List中成績及格的對象的username: <s:property value="stus.{?#this.grade>=60}.{username}"/><br> 利用選擇獲取List中成績及格的第一個對象的username: <s:property value="stus.{?#this.grade>=60}.{username}[0]"/><br> 利用選擇獲取List中成績及格的第一個對象的username: <s:property value="stus.{^#this.grade>=60}.{username}"/><br> 利用選擇獲取List中成績及格的最後一個對象的username: <s:property value="stus.{$#this.grade>=60}.{username}"/><br> 利用選擇獲取List中成績及格的第一個對象然後求大小: <s:property value="stus.{^#this.grade>=600}.{username}.size"/><br> |
12. 在Struts2中,根對象就是ValueStack。在Struts2的任何流程當中,ValueStack中的最頂層對象一定是Action對象。
13. parameters,#parameters.username
request, #request.username
session, #session.username
application, #application.username
attr, #attr.username
以上幾個對象叫做“命名對象”。
14. 訪問靜態方法或是靜態成員變量的改進。
@vs@method
15. 關於Struts2標籤庫屬性值的%與#號的關係:
1). 如果標籤的屬性值是OGNL表達式,那麼無需加上%{}。
2). 如果標籤的屬性值是字符串類型,那麼在字符串當中凡是出現的%{}都會被解析成OGNL表達式,解析完畢後再與其他的字符串進行拼接構造出最後的字符串值。
3). 我們可以在所有的屬性值上加%{},這樣如果該屬性值是OGNL表達式,那麼標籤處理類就會將%{}忽略掉。
最後一起用代碼說話,簡單的看一下ognl操作的示例:
1)上下文環境中使用OGNL
- public static void main(String[] args)
- {
- /* 創建一個上下文Context對象,它是用保存多個對象一個環境 對象*/
- Map<String , Object> context = new HashMap<String , Object>();
- Person person1 = new Person();
- person1.setName("zhangsan");
- Person person2 = new Person();
- person2.setName("lisi");
- Person person3 = new Person();
- person3.setName("wangwu");
- /* person4不放入到上下文環境中*/
- Person person4 = new Person();
- person4.setName("zhaoliu");
- /* 將person1、person2、person3添加到環境中(上下文中)*/
- context.put("person1", person1);
- context.put("person2", person2);
- context.put("person3", person3);
- try
- {
- /* 獲取根對象的"name"屬性值*/
- Object value = Ognl.getValue("name", context, person2);
- System.out.println("ognl expression \"name\" evaluation is : " + value);
- /* 獲取根對象的"name"屬性值*/
- Object value2 = Ognl.getValue("#person2.name", context, person2);
- System.out.println("ognl expression \"#person2.name\" evaluation is : " + value2);
- /* 獲取person1對象的"name"屬性值*/
- Object value3 = Ognl.getValue("#person1.name", context, person2);
- System.out.println("ognl expression \"#person1.name\" evaluation is : " + value3);
- /* 將person4指定爲root對象,獲取person4對象的"name"屬性,注意person4對象不在上下文中*/
- Object value4 = Ognl.getValue("name", context, person4);
- System.out.println("ognl expression \"name\" evaluation is : " + value4);
- /* 將person4指定爲root對象,獲取person4對象的"name"屬性,注意person4對象不在上下文中*/
- Object value5 = Ognl.getValue("#person4.name", context, person4);
- System.out.println("ognl expression \"person4.name\" evaluation is : " + value5);
- /* 獲取person4對象的"name"屬性,注意person4對象不在上下文中*/
- // Object value6 = Ognl.getValue("#person4.name", context, person2);
- // System.out.println("ognl expression \"#person4.name\" evaluation is : " + value6);
- }
- public static void main(String[] args)
- {
- /* 創建一個上下文Context對象,它是用保存多個對象一個環境 對象*/
- Map<String , Object> context = new HashMap<String , Object>();
- Person person1 = new Person();
- person1.setName("zhangsan");
- Person person2 = new Person();
- person2.setName("lisi");
- Person person3 = new Person();
- person3.setName("wangwu");
- /* person4不放入到上下文環境中*/
- Person person4 = new Person();
- person4.setName("zhaoliu");
- /* 將person1、person2、person3添加到環境中(上下文中)*/
- context.put("person1", person1);
- context.put("person2", person2);
- context.put("person3", person3);
- try
- {
- /* 獲取根對象的"name"屬性值*/
- Object value = Ognl.getValue("name", context, person2);
- System.out.println("ognl expression \"name\" evaluation is : " + value);
- /* 獲取根對象的"name"屬性值*/
- Object value2 = Ognl.getValue("#person2.name", context, person2);
- System.out.println("ognl expression \"#person2.name\" evaluation is : " + value2);
- /* 獲取person1對象的"name"屬性值*/
- Object value3 = Ognl.getValue("#person1.name", context, person2);
- System.out.println("ognl expression \"#person1.name\" evaluation is : " + value3);
- /* 將person4指定爲root對象,獲取person4對象的"name"屬性,注意person4對象不在上下文中*/
- Object value4 = Ognl.getValue("name", context, person4);
- System.out.println("ognl expression \"name\" evaluation is : " + value4);
- /* 將person4指定爲root對象,獲取person4對象的"name"屬性,注意person4對象不在上下文中*/
- Object value5 = Ognl.getValue("#person4.name", context, person4);
- System.out.println("ognl expression \"person4.name\" evaluation is : " + value5);
- /* 獲取person4對象的"name"屬性,注意person4對象不在上下文中*/
- // Object value6 = Ognl.getValue("#person4.name", context, person2);
- // System.out.println("ognl expression \"#person4.name\" evaluation is : " + value6);
- }
2)使用OGNL調用方法
- public static void main(String[] args)
- {
- /* OGNL提供的一個上下文類,它實現了Map接口*/
- OgnlContext context = new OgnlContext();
- People people1 = new People();
- people1.setName("zhangsan");
- People people2 = new People();
- people2.setName("lisi");
- People people3 = new People();
- people3.setName("wangwu");
- context.put("people1", people1);
- context.put("people2", people2);
- context.put("people3", people3);
- context.setRoot(people1);
- try
- {
- /* 調用 成員方法*/
- Object value = Ognl.getValue("name.length()", context, context.getRoot());
- System.out.println("people1 name length is :" + value);
- Object upperCase = Ognl.getValue("#people2.name.toUpperCase()", context, context.getRoot());
- System.out.println("people2 name upperCase is :" + upperCase);
- Object invokeWithArgs = Ognl.getValue("name.charAt(5)", context, context.getRoot());
- System.out.println("people1 name.charAt(5) is :" + invokeWithArgs);
- /* 調用靜態方法*/
- Object min = Ognl.getValue("@java.lang.Math@min(4,10)", context, context.getRoot());
- System.out.println("min(4,10) is :" + min);
- /* 調用靜態變量*/
- Object e = Ognl.getValue("@java.lang.Math@E", context, context.getRoot());
- System.out.println("E is :" + e);
- }
- public static void main(String[] args)
- {
- /* OGNL提供的一個上下文類,它實現了Map接口*/
- OgnlContext context = new OgnlContext();
- People people1 = new People();
- people1.setName("zhangsan");
- People people2 = new People();
- people2.setName("lisi");
- People people3 = new People();
- people3.setName("wangwu");
- context.put("people1", people1);
- context.put("people2", people2);
- context.put("people3", people3);
- context.setRoot(people1);
- try
- {
- /* 調用 成員方法*/
- Object value = Ognl.getValue("name.length()", context, context.getRoot());
- System.out.println("people1 name length is :" + value);
- Object upperCase = Ognl.getValue("#people2.name.toUpperCase()", context, context.getRoot());
- System.out.println("people2 name upperCase is :" + upperCase);
- Object invokeWithArgs = Ognl.getValue("name.charAt(5)", context, context.getRoot());
- System.out.println("people1 name.charAt(5) is :" + invokeWithArgs);
- /* 調用靜態方法*/
- Object min = Ognl.getValue("@java.lang.Math@min(4,10)", context, context.getRoot());
- System.out.println("min(4,10) is :" + min);
- /* 調用靜態變量*/
- Object e = Ognl.getValue("@java.lang.Math@E", context, context.getRoot());
- System.out.println("E is :" + e);
- }
3)使用OGNL操作集合
- public static void main(String[] args) throws Exception
- {
- OgnlContext context = new OgnlContext();
- Classroom classroom = new Classroom();
- classroom.getStudents().add("zhangsan");
- classroom.getStudents().add("lisi");
- classroom.getStudents().add("wangwu");
- classroom.getStudents().add("zhaoliu");
- classroom.getStudents().add("qianqi");
- Student student = new Student();
- student.getContactWays().put("homeNumber", "110");
- student.getContactWays().put("companyNumber", "119");
- student.getContactWays().put("mobilePhone", "112");
- context.put("classroom", classroom);
- context.put("student", student);
- context.setRoot(classroom);
- /* 獲得classroom的students集合*/
- Object collection = Ognl.getValue("students", context, context.getRoot());
- System.out.println("students collection is :" + collection);
- /* 獲得classroom的students集合*/
- Object firstStudent = Ognl.getValue("students[0]", context, context.getRoot());
- System.out.println("first student is : " + firstStudent);
- /* 調用集合的方法*/
- Object size = Ognl.getValue("students.size()", context, context.getRoot());
- System.out.println("students collection size is :" + size);
- System.out.println("--------------------------飄逸的分割線--------------------------");
- Object mapCollection = Ognl.getValue("#student.contactWays", context, context.getRoot());
- System.out.println("mapCollection is :" + mapCollection);
- Object firstElement = Ognl.getValue("#student.contactWays['homeNumber']", context, context.getRoot());
- System.out.println("the first element of contactWays is :" + firstElement);
- System.out.println("--------------------------飄逸的分割線--------------------------");
- /* 創建集合*/
- Object createCollection = Ognl.getValue("{'aa','bb','cc','dd'}", context, context.getRoot());
- System.out.println(createCollection);
- /* 創建Map集合*/
- Object createMapCollection = Ognl.getValue("#{'key1':'value1','key2':'value2'}", context, context.getRoot());
- System.out.println(createMapCollection);
- }
- }