Struts2第二天,大家似乎沒有從年假中蘇醒過來,上課顯得有些疲憊。不過還好,聽課效果還不錯(cuò)。今日的主要內(nèi)容是OGNL,Struts2使用OGNL訪問contextMap和valueStack。老張很執(zhí)著,他在研究Struts2時(shí)遇到的一些問題,一定要解決,并且要詳細(xì)解決。通過他在講課中,我們可以看出這一點(diǎn)。有開發(fā)經(jīng)驗(yàn)的人都知道,我們時(shí)而被陌生技術(shù)的一個(gè)小細(xì)節(jié)搞的暈頭轉(zhuǎn)向,耗費(fèi)了大半天的時(shí)間我們才發(fā)現(xiàn)這個(gè)細(xì)節(jié)性的錯(cuò)誤。我們一般都是這么熬出來的,老張也不例外。
我們?cè)诼犂蠋熤v課時(shí)或在網(wǎng)上看視頻時(shí),都感覺學(xué)習(xí)起來十分容易。卻很少有人能感受那些認(rèn)真負(fù)責(zé)老師在課后付出的心血。老張為了準(zhǔn)備一天的課程內(nèi)容,使用了三天時(shí)間來備課并且課程內(nèi)容他以前也講過。這一點(diǎn)不僅體現(xiàn)在老張的身上,其他老師也是這樣。讓我們?yōu)槟切┱J(rèn)真負(fù)責(zé)的老師敬禮,并問候一聲:“過年好,您辛苦了!”。
一、ActionContext
Action環(huán)境(com.opensymphony.xwork2.ActionContext),通過我們之前所學(xué)的內(nèi)容。我們知道Context緩存中保存了XXX環(huán)境中所需要使用到的重要對(duì)象。Struts2中的ActionContext中就保存了Struts2中使用到的重點(diǎn)對(duì)象contextMap和valueStack。
老張搞了一個(gè)經(jīng)典的關(guān)系圖:

ActionContext ac = ActionContext.getContext();
ValueStack vs = ac.getValueStack();
Map<String, Object> conMap = ac.getContextMap();
每一個(gè)訪問線程具有一個(gè)ActionContext對(duì)象,它是以單例模式存在的。ActionContext為我們提供了操作valueStack和contextMap的便捷方法。例我們的程序可以與ValueStack和ContextMap解耦,比如:ac.put(key, value);、ac.get(key);...。
通過ActionContext使得我們編寫的Action不必實(shí)現(xiàn)任何接口和繼承任何超類,但我們必須在類中使用ActionContext的實(shí)例。
二、Struts2中的OGNL
OGNL不是Struts2獨(dú)創(chuàng)的,而是Struts2中使用OGNL做為操作contextMap和valueStack的方式。OGNL是Object-Graph Navigation Language的縮寫,它是一種功能強(qiáng)大的表達(dá)式語(yǔ)言(Expression Language,簡(jiǎn)稱為EL),通過它簡(jiǎn)單一致的表達(dá)式語(yǔ)法,可以存取對(duì)象的任意屬性,調(diào)用對(duì)象的方法,遍歷整個(gè)對(duì)象的結(jié)構(gòu)圖,實(shí)現(xiàn)字段類型轉(zhuǎn)化等功能。它使用相同的表達(dá)式去存取對(duì)象的屬性。
OGNLl基本的語(yǔ)法:
-
可以用#key的形式訪問OGNL Context對(duì)象中的各個(gè)key對(duì)應(yīng)的對(duì)象,并可以采用點(diǎn)(.)操作符進(jìn)行多級(jí)導(dǎo)航調(diào)用對(duì)象的屬性和方法,例如,#application、#session.attr1、#key1.sayHello();對(duì)于map對(duì)象,map.attr不是map.getAttr()方法,而是表示map.get(“attr1”)。
-
如果要訪問根對(duì)象的屬性或方法,則可以省略#key,直接訪問該對(duì)象的屬性和方法。 struts2修改了OGNL表達(dá)式的默認(rèn)屬性訪問器,它不是直接訪問根對(duì)象ValueStack的屬性或方法,而是在ValueStack內(nèi)部的堆棧中所有對(duì)象上逐一查找該屬性或方法,搜索順序是從棧頂對(duì)象開始尋找,依次往下,直到找到為止,例如,sayHello()表示調(diào)用堆棧中某個(gè)對(duì)象的sayHello()方法。
-
特例:如果引用名前面沒有#,且valueStack中存儲(chǔ)的各個(gè)對(duì)象沒有該屬性,則把該名稱當(dāng)作Context對(duì)象中的某個(gè)key來檢索對(duì)應(yīng)的對(duì)象,但這種方式不支持點(diǎn)(.)操作符。
ValueStack提供了如下一些方法管理其內(nèi)部的堆棧和關(guān)聯(lián)的Context:
-
setValue為ognl表達(dá)式尋址到的對(duì)象設(shè)置屬性值。
-
FindValue方法使用OGNL表達(dá)式獲取結(jié)果。
-
findString方法對(duì)findValue方法獲取的結(jié)果調(diào)用轉(zhuǎn)換器轉(zhuǎn)成字符串,如果該對(duì)象的類型沒有相關(guān)轉(zhuǎn)換器,則調(diào)用toString方法,并返回結(jié)果字符串。一個(gè)特殊之處:如果不用#前綴訪問ValueStack Context中的對(duì)象,則該對(duì)象必須是String類型。
三、Struts2中的struts-tags與OGNL詳細(xì)
要使用struts-tags標(biāo)簽,必須先引入struts-2.1.7.dtd文檔類型定義文件。
-
<s:property>:用于輸出某個(gè)OGNL表達(dá)式的值,可以認(rèn)為其內(nèi)部使用的是ValueStack對(duì)象的findString方法。如果沒有設(shè)置value屬性,則輸出ValueStack棧頂?shù)膶?duì)象。
特例:如果采用不加#前綴的方式輸出Context中的某個(gè)對(duì)象,這個(gè)對(duì)象必須是string類型。
如:獲取contextMap中的request對(duì)象。
<s:property value="#request" />
|
-
<s:push>:用于將OGNL表達(dá)式的值壓入棧頂,當(dāng)遇到</s:push>標(biāo)簽時(shí),從棧頂彈出。
如:向棧頂壓入了一個(gè)字符串——“你好!”。
<s:push value="'你好!'">
<s:property value="top" />
</s:push>
|
注意壓入的字符串必須被包含在''中,否則會(huì)被OGNL當(dāng)做名稱到根對(duì)象中查找相應(yīng)的值。
通過查看<s:push>標(biāo)簽的幫助,可以知道有一個(gè)為top的特殊OGNL表達(dá)式,表示棧頂?shù)膶?duì)象。
如:
<s:push value="'你好!'"><s:property name="top"/></s:push>
|
-
<s:bean>:用于實(shí)例化一個(gè)JavaBean對(duì)象,并將其壓入棧頂。如果設(shè)置了var屬性,還會(huì)將實(shí)例化的對(duì)象存儲(chǔ)進(jìn)ValueStack關(guān)聯(lián)的Context中。
如:實(shí)例化一個(gè)java.util.Date對(duì)象,然后將其放入ContextMap中。
<s:bean name="java.util.Date" var="MyBean">
<s:property value="#MyBean" />
</s:bean>
|
-
<s:set>:用于將某個(gè)值存入指定范圍域中,通常用于將一個(gè)復(fù)雜的ognl表達(dá)式用一個(gè)簡(jiǎn)單的變量來進(jìn)行引用。
如:向request域中添加一個(gè)名稱為“TestSet”的屬性。
<s:set scope="request" value="'日期'+#MyBean" name="TestSet">
<s:property value="#request.TestSet" />
</s:set>
|
scope屬性:指定變量被放置的范圍,該屬性可以接受application、session、request、 page或action。該屬性的默認(rèn)值為action,文檔說即表示同時(shí)存儲(chǔ)進(jìn)request作用域和OGNL Context中,但實(shí)驗(yàn)結(jié)果是只存儲(chǔ)進(jìn)了OGNL Context中。
value屬性:賦給變量的ognl表達(dá)式結(jié)果.如果沒有設(shè)置該屬性,則將ValueStack棧頂?shù)闹蒂x給變量。
-
<s:if/elseif/else>:等標(biāo)簽用于判斷test屬性中指定的ognl表達(dá)式的結(jié)果是否為true,為真則執(zhí)行標(biāo)簽體重的內(nèi)容。
如:設(shè)置一個(gè)VAL,然后判斷VAL的值,并顯示相應(yīng)的結(jié)果。
<s:set value="'1'" name="VAL"></s:set>
<s:if test="1 > 2">
<s:property value="#VAL+'>2'" />
</s:if>
<s:elseif test="1 < 2">
<s:property value="#VAL+'<2'" />
</s:elseif>
<s:else>
<s:property value="#VAL+'=2'" />
</s:else>
|
-
<s:iterator>:用于迭代一個(gè)OGNL集合,并逐一將迭代出來的元素壓入棧頂和彈棧。
如:獲取request中的所有屬性,并且使表格奇數(shù)行顏色為#f3c3c3,偶數(shù)行顏色為#c3f3f3。
<table>
<tr>
<td>Key</td><td>Value</td>
</tr>
<s:iterator value="#request" status="status">
<tr bgcolor='<s:property value="#status.odd ? '#f3c3c3':'#c3f3f3'"/>' >
<td><s:property value="key"/></td>
<td><s:property value="value"/></td>
</tr>
</s:iterator>
</table>
|
status屬性:創(chuàng)建代表當(dāng)前迭代狀態(tài)的IteratorStatus對(duì)象,并指定將其存儲(chǔ)進(jìn)ValueStack Context中時(shí)的key。它與官方給出的幫助文檔的說明不同,官方存在錯(cuò)誤!
-
<s:url>和<s:a>:
使用這兩個(gè)標(biāo)簽可以帶給我們極大的方便,使用他們的好處有:
使用<s:url namespace=“” action=“” method=“”/>不用關(guān)心web應(yīng)用程序的路徑和Action映射的擴(kuò)展名。<s:url>中沒有指定namespace屬性時(shí),將根據(jù)瀏覽器當(dāng)前所訪問的url地址來推測(cè)包名,并與action屬性指定的內(nèi)容一起生成最終的url地址。
可以自動(dòng)附加jsessionid參數(shù)進(jìn)行url重寫。
可以對(duì)參數(shù)信息進(jìn)行url編碼。 (jstl中的<c:url>標(biāo)準(zhǔn)標(biāo)簽也有<s:url>標(biāo)簽的后兩個(gè)作用。)
直接使用<s:url />標(biāo)簽可以獲得當(dāng)前地址,只有使用includeParams屬性會(huì)帶上原來的請(qǐng)求參數(shù)。大型網(wǎng)站的鏈接地址總是要帶上userid之類的信息。
如:生成一個(gè)訪問我們昨天編寫的HelloWorld請(qǐng)求超連接。
<s:a namespace="/" action="helloWorld" method="helloWorld">HelloWorld</s:a>
|
四、OGNL中的語(yǔ)法細(xì)節(jié)
1.參看ognl的參考手冊(cè)
類似EL表達(dá)式的JavaBean屬性訪問和索引訪問,例如,可以用”#parameter.id[0]”或”#parameter[‘id’][0]”訪問名稱為id的請(qǐng)求參數(shù)。
支持類靜態(tài)方法調(diào)用和屬性訪問,表達(dá)式的格式為@[類全名(包括包路徑)]@[方法名 | 值名],例如:@java.lang.String@format(‘foo %s’, ‘bar’)或@cn.itcast.Constant@APP_NAME;
session.attribute[“foo”]等效于session.getAttribute(“foo”)方法。
在OGNL中可以寫很大的整數(shù),例如,<s:property value="%{1111111111111111111111H.bitLength()}"/>,而在java中則不能直接寫1111111111111111111111這么大的整數(shù)。
對(duì)當(dāng)前值可以進(jìn)一步操作,<s:property value=“110H.intValue().(#this<112?#this*2:#this/2)”/>,其中.(#this …..)部分相當(dāng)于定義了一個(gè)匿名方法,并調(diào)用這個(gè)匿名方法,方法的代碼就是()里面的內(nèi)容。
2.Struts2擴(kuò)展的特殊功能
[n]表示從原來堆棧中截取一個(gè)子堆棧并對(duì)這個(gè)子堆棧進(jìn)行操作,子堆棧為原始堆棧的棧頂開始的索引號(hào)為n的元素一直到棧底,例如,[1].age表示從原始堆棧中的索引號(hào)為1的對(duì)象(即第二個(gè)對(duì)象)開始查找age屬性,以找到的第一個(gè)為準(zhǔn)。
top表示ValueStack棧頂?shù)膶?duì)象,[0].top和top表示同一個(gè)對(duì)象。
3.集合對(duì)象與操作
{}用于創(chuàng)建List集合對(duì)象,其中的各個(gè)元素之間用逗號(hào)分隔:
<s:set value="{1,3,5,7}" var="list"/>
|
采用類似Java的語(yǔ)法創(chuàng)建數(shù)組:
<s:set value="new int[]{1,3,5,7}" var="array"/>
<s:set value="new int[4]" var="array"/>
|
#{}用于創(chuàng)建Map集合對(duì)象,其中的各個(gè)元素之間用逗號(hào)分隔,元素的key和value之間采用冒號(hào)分隔。另外,還可以指定Map實(shí)例對(duì)象的類型:
<s:set value="#{'lhm':96,'zxx':93,'xpc':97}" />
<s:set value="#@java.util.LinkedHashMap@{'lhm':96,'zxx':93,'xpc':97}" />
|
in與not in操作符用于判斷某個(gè)值是否位于某個(gè)集合中。
<s:set value="new int[]{1,3,5,7}" var="array"/>
<s:if test="1 in #array">
<s:property value="#array"/>
</s:if>
|
集合偽屬性:size/isEmpty/iterator/keys/values/next/hasNext。
<s:set value="#{'lhm':96,'zxx':93,'xpc':97}" var="map"/>
<s:if test="#map.size > 0">
<s:property value="#map"/>
</s:if>
|
4.集合的投影與過濾
投影就是拿著集合中的每個(gè)元素去進(jìn)行運(yùn)算,各個(gè)元素運(yùn)算的結(jié)果組成一個(gè)新集合,新集合中的元素個(gè)數(shù)與原始集合中的元素個(gè)數(shù)相同。
<s:property value="persons.{name}"/>
<s:property value="{5,3,2}.{#this*2}"/>
|
過濾就是拿著集合中的每個(gè)元素去進(jìn)行布爾運(yùn)算,運(yùn)算的結(jié)果為true,則將該元素保存到新集合中去。
?:獲得所有符合邏輯的元素。
<s:property value="{5,3,2,0}.{? #this }"/>
|
^:獲得符合邏輯的第一個(gè)元素。
<s:property value="{5,3,2,0}.{^ #this>3 }"/>
|
$:獲得符合邏輯的最后一個(gè)元素。
<s:property value="{5,3,2,0}.{$ #this>2 }"/>
|
5.類型轉(zhuǎn)換
轉(zhuǎn)換成boolean類型:
投影和選擇操作符(e1.{e2} and e1.{?e2})里面的內(nèi)容會(huì)被轉(zhuǎn)換成集合
Map會(huì)被轉(zhuǎn)化成其values屬性返回的集合
數(shù)字會(huì)被轉(zhuǎn)換成從0開始到比該數(shù)字小1的所有數(shù)字的集合。
單個(gè)對(duì)象被轉(zhuǎn)換成僅僅只含有該對(duì)象的集合。
五、在配置文件中使用OGNL
在下堂課中會(huì)做總結(jié)!