Struts2的OGNL表達式語言
1.OGNL表達式語言
OGNL是Object Graphic Navigation Language(對象圖導航語言)的縮寫,它是一個開源項目。 Struts 2框架使用OGNL作為默認的表達式語言。
相對EL表達式,它提供了平時我們需要的一些功能,如:
支持對象方法調用,如xxx.sayHello();
支持類靜態方法調用和值訪問,表達式的格式為@[類全名(包括包路徑)]@[方法名 | 值名],例如:@java.lang.String@format('foo %s', 'bar')或@cn.itcast.Constant@APP_NAME;
操作集合對象
Ognl 有一個上下文(Context)概念,說白了上下文就是一個MAP結構,它實現了java.utils.Map接口,在Struts2中上下文(Context)的實現為ActionContext,下面是上下文(Context)的結構示意圖
2.訪問上下文(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"/>
3.由于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順序訪問其屬性。
4.為何使用EL表達式能夠訪問valueStack中對象的屬性
原因是Struts2對HttpServletRequest作了進一步的封裝。簡略代碼如下:
public class StrutsRequestWrapper extends HttpServletRequestWrapper {
public StrutsRequestWrapper(HttpServletRequest req) {
super(req);
}
public Object getAttribute(String s) {
......
ActionContext ctx = ActionContext.getContext();
Object attribute = super.getAttribute(s);//先從request范圍獲取屬性值
if (ctx != null) {
if (attribute == null) {//如果從request范圍沒有找到屬性值,即從ValueStack中查找對象的屬性值
......
ValueStack stack = ctx.getValueStack();
attribute = stack.findValue(s);
......
}
}
return attribute;
}
}
5.采用OGNL表達式創建List/Map集合對象
如果需要一個集合元素的時候(例如List對象或者Map對象),可以使用OGNL中同集合相關的表達式。
使用如下代碼直接生成一個List對象:
<s:set name="list" value="{'zhangming','xiaoi','liming'}" />
<s:iterator value="#list" id="n">
<s:property value="n"/><br>
</s:iterator>
生成一個Map對象:
<s:set name="foobar" value="#{'foo1':'bar1', 'foo2':'bar2'}" />
<s:iterator value="#foobar" >
<s:property value="key"/>=<s:property value="value"/><br>
</s:iterator>
Set標簽用于將某個值放入指定范圍。
scope:指定變量被放置的范圍,該屬性可以接受application、session、request、 page或action。如果沒有設置該屬性,則默認放置在OGNL Context中。
value:賦給變量的值.如果沒有設置該屬性,則將ValueStack棧頂的值賦給變量。
6.采用OGNL表達式判斷對象是否存在于集合中
對于集合類型,OGNL表達式可以使用in和not in兩個元素符號。其中,in表達式用來判斷某個元素是否在指定的集合對象中;not in判斷某個元素是否不在指定的集合對象中,如下所示。
in表達式:
<s:if test="'foo' in {'foo','bar'}">
在
</s:if>
<s:else>
不在
</s:else>
not in表達式:
<s:if test="'foo' not in {'foo','bar'}">
不在
</s:if>
<s:else>
在
</s:else>
7.OGNL表達式的投影功能
除了in和not in之外,OGNL還允許使用某個規則獲得集合對象的子集,常用的有以下3個相關操作符。
?:獲得所有符合邏輯的元素。
^:獲得符合邏輯的第一個元素。
$:獲得符合邏輯的最后一個元素。
例如代碼:
<s:iterator value="books.{?#this.price > 35}">
<s:property value="title" /> - $<s:property value="price" /><br>
</s:iterator>
在上面代碼中,直接在集合后緊跟.{}運算符表明用于取出該集合的子集,{}內的表達式用于獲取符合條件的元素,this指的是為了從大集合books篩選數據到小集合,需要對大集合books進行迭代,this代表當前迭代的元素。本例的表達式用于獲取集合中價格大于35的書集合。
public class BookAction extends ActionSupport {
private List<Book> books;
....
@Override
public String execute() {
books = new LinkedList<Book>();
books.add(new Book("A735619678", "spring", 67));
books.add(new Book("B435555322", "ejb3.0",15));
}
}
8.property標簽
property標簽用于輸出指定值:
<s:set name="name" value="'kk'" />
<s:property value="#name"/>
default:可選屬性,如果需要輸出的屬性值為null,則顯示該屬性指定的值
escape:可選屬性,指定是否格式化HTML代碼。
value:可選屬性,指定需要輸出的屬性值,如果沒有指定該屬性,則默認輸出ValueStack棧頂的值。
id:可選屬性,指定該元素的標識
9. iterator標簽
iterator標簽用于對集合進行迭代,這里的集合包含List、Set和數組。
<s:set name="list" value="{'zhangming','xiaoi','liming'}" />
<s:iterator value="#list" status="st">
<font color=<s:if test="#st.odd">red</s:if><s:else>blue</s:else>>
<s:property /></font><br>
</s:iterator>
value:可選屬性,指定被迭代的集合,如果沒有設置該屬性,則使用ValueStack棧頂的集合。
id:可選屬性,指定集合里元素的id。
status:可選屬性,該屬性指定迭代時的IteratorStatus實例。該實例包含如下幾個方法:
int getCount(),返回當前迭代了幾個元素。
int getIndex(),返回當前迭代元素的索引。
boolean isEven(),返回當前被迭代元素的索引是否是偶數
boolean isOdd(),返回當前被迭代元素的索引是否是奇數
boolean isFirst(),返回當前被迭代元素是否是第一個元素。
boolean isLast(),返回當前被迭代元素是否是最后一個元素。
10.if/elseif/else標簽
<s:set name="age" value="21" />
<s:if test="#age==23">
23
</s:if>
<s:elseif test="#age==21">
21
</s:elseif>
<s:else>
都不等
</s:else>
10.url標簽
<s:url action="helloworld_add" namespace="/test"><s:param name="personid" value="23"/></s:url>
生成類似如下路徑:
/struts/test/helloworld_add.action?personid=23
紅色部分為內容路徑。
當標簽的屬性值作為字符串類型處理時, “%”符號的用途是計算OGNL表達式的值。
<s:set name="myurl" value="'http://www.foshanshop.net'"/>
<s:url value="#myurl" /><br>
<s:url value="%{#myurl}" />
輸出結果:
#myurl
http://www.foshanshop.net
11.表單標簽_checkboxlist復選框
如果集合為list
<s:checkboxlist name="list" list="{'Java','.Net','RoR','PHP'}" value="{'Java','.Net'}"/>
生成如下html代碼:
<input type="checkbox" name="list" value="Java" checked="checked"/><label>Java</label>
<input type="checkbox" name="list" value=".Net" checked="checked"/><label>.Net</label>
<input type="checkbox" name="list" value="RoR"/><label>RoR</label>
<input type="checkbox" name="list" value="PHP"/><label>PHP</label>
如果集合為MAP
<s:checkboxlist name="map" list="#{1:'瑜珈用品',2:'戶外用品',3:'球類',4:'自行車'}" listKey="key" listValue="value" value="{1,2,3}"/>
生成如下html代碼:
<input type="checkbox" name="map" value="1" checked="checked"/><label>瑜珈用品</label>
<input type="checkbox" name="map" value="2" checked="checked"/><label>戶外用品</label>
<input type="checkbox" name="map" value="3" checked="checked"/><label>球類</label>
<input type="checkbox" name="map" value="4"/><label>自行車</label>
12. 表單標簽_checkboxlist復選框
如果集合里存放的是javabean
<%
Person person1 = new Person(1,"第一個");
Person person2 = new Person(2,"第二個");
List<Person> list = new ArrayList<Person>();
list.add(person1);
list.add(person2);
request.setAttribute("persons",list);
%>
<s:checkboxlist name="beans" list="#request.persons" listKey="personid" listValue="name"/>
Personid和name為Person的屬性
生成如下html代碼:
<input type="checkbox" name=“beans" value="1"/><label>第一個</label>
<input type="checkbox" name=“beans" value="2"/><label>第二個</label>
13.表單標簽_radio單選框
該標簽的使用和checkboxlist復選框相同。
如果集合里存放的是javabean(personid和name為Person的屬性)
< s:radio name="beans" list="#request.persons" listKey="personid" listValue="name"/>
生成如下html代碼:
<input type="radio" name="beans" id="beans1" value="1"/><label>第一個</label>
<input type="radio" name="beans" id="beans2" value="2"/><label>第二個</label>
如果集合為MAP
<s:radio name="map" list="#{1:'瑜珈用品',2:'戶外用品',3:'球類',4:'自行車'}" listKey="key" listValue="value“ value="1"/>
生成如下html代碼:
<input type="radio" name="map" id="map1" value="1"/><label for="map1">瑜珈用品</label>
<input type="radio" name="map" id="map2" value="2"/><label for="map2">戶外用品</label>
<input type="radio" name="map" id="map3" value="3"/><label for="map3">球類</label>
<input type="radio" name="map" id="map4" value="4"/><label for="map4">自行車</label>
如果集合為list
<s:radio name="list" list="{'Java','.Net'}" value="'Java'"/>
生成如下html代碼:
<input type="radio" name="list" checked="checked" value="Java"/><label>Java</label>
<input type="radio" name="list" value=".Net"/><label>.Net</label>
14.表單標簽_select下拉選擇框
<s:select name="list" list="{'Java','.Net'}" value="'Java'"/>
<select name="list" id="list">
<option value="Java" selected="selected">Java</option>
<option value=".Net">.Net</option>
</select>
<s:select name="beans" list="#request.persons" listKey="personid" listValue="name"/>
<select name="beans" id="beans">
<option value="1">第一個</option>
<option value="2">第二個</option>
</select>
<s:select name="map" list="#{1:'瑜珈用品',2:'戶外用品',3:'球類',4:'自行車'}" listKey="key" listValue="value" value="1"/>
<select name="map" id="map">
<option value="1" selected="selected">瑜珈用品</option>
<option value="2">戶外用品</option>
<option value="3">球類</option>
<option value="4">自行車</option>
</select>
struts2 OGNL表達式
Struts2 2010-01-29 11:03:00 閱讀301 評論0 字號:大中小
OGNL是Object Graph Navigation Language的簡稱,詳細相關的信息可以參考:http://www.ognl.org。這里我們只涉及Struts2框架中對OGNL的基本支持。
Struts 2默認的表達式語言是OGNL,原因是它相對其它表達式語言具有下面幾大優勢:
1. 支持對象方法調用,如xxx.doSomeSpecial();
2. 支持類靜態的方法調用和值訪問,表達式的格式為@[類全名(包括包路徑)]@[方法名 | 值名],例如:@java.lang.String@format('foo %s', 'bar')或@tutorial.MyConstant@APP_NAME;
3. 支持賦值操作和表達式串聯,如price=100, discount=0.8, calculatePrice(),這個表達式會返回80;
4. 訪問OGNL上下文(OGNL context)和ActionContext;
5. 操作集合對象。
#、%和$這三個符號的使用:
“#”主要有三種用途:
1. 訪問OGNL上下文和Action上下文,#相當于ActionContext.getContext();下表有幾個ActionContext中有用的屬性:
名稱 |
作用 |
例子 |
parameters |
包含當前HTTP請求參數的Map |
#parameters.id[0]作用相當于request.getParameter("id") |
request |
包含當前HttpServletRequest的屬性(attribute)的Map |
#request.userName相當于request.getAttribute("userName") |
session |
包含當前HttpSession的屬性(attribute)的Map |
#session.userName相當于session.getAttribute("userName") |
application |
包含當前應用的ServletContext的屬性(attribute)的Map |
#application.userName相當于application.getAttribute("userName") |
attr |
用于按request > session > application順序訪問其屬性(attribute) |
#attr.userName相當于按順序在以上三個范圍(scope)內讀取userName屬性,直到找到為止 |
2. 用于過濾和投影(projecting)集合,如books.{?#this.price<100};
3. 構造Map,如#{'foo1':'bar1', 'foo2':'bar2'}。
“%”符號的用途是在標志的屬性為字符串類型時,計算OGNL表達式的值。例如在Ognl.jsp中加入以下代碼:
<hr />
<h3>%的用途</h3>
<p><s:url value="#foobar['foo1']" /></p>
<p><s:url value="%{#foobar['foo1']}" /></p>
OGNL是一個對象,屬性的查詢語言。在OGNL中有一個類型為Map的Context(稱為上下文),在這個上下文中有一個根元素(root),對根元素的屬性的訪問可以直接使用屬性名字,但是對于其他非根元素屬性的訪問必須加上特殊符號#。
在Struts2中上下文為ActionContext,根元素位Value Stack(值堆棧,值堆棧代表了一族對象而不是一個對象,其中Action類的實例也屬于值堆棧的一個)。ActionContext中的內容如下圖:
|--application
|
|--session
context map---|
|--value stack(root)
|
|--request
|
|--parameters
|
|--attr (searches page, request, session, then application scopes)
因為Action實例被放在Value Stack中,而Value Stack又是根元素(root)中的一個,所以對Action中的屬性的訪問可以不使用標記#,而對其他的訪問都必須使用#標記。
引用Action的屬性
<s:property value="postalCode"/>
ActionContext中的其他非根(root)元素的屬性可以按照如下的方式訪問:
<s:property value="#session.mySessionPropKey"/> or
<s:property value="#session["mySessionPropKey"]"/> or
<s:property value="#request["mySessionPropKey"]/>
Action類可以使用ActionContext中的靜態方法來訪問ActionContext。
ActionContext.getContext().getSession().put("mySessionPropKey", mySessionObject);
OGNL與Collection(Lists,Maps,Sets)
生成List的語法為: {e1,e2,e3}.
<s:select label="label" name="name"
list="{'name1','name2','name3'}" value="%{'name2'}" />
上面的代碼生成了一個HTML Select對象,可選的內容為: name1,name2,name3,默認值為:name2。
生成Map的語法為:#{key1:value1,key2:value2}.
<s:select label="label" name="name"
list="#{'foo':'foovalue', 'bar':'barvalue'}" />
上面的代碼生成了一個HTML Select對象,foo名字表示的內容為:foovalue,bar名字表示的內容為:barvalue。
對于集合,OGNL提供了兩個元素符:in和not in,其中in判斷某個元素是否在指定集合中;not in則用于判斷某個元素是否不在指定集合中。
判斷一個對象是否在List內存在:
<s:if test="'foo' in {'foo','bar'}">
muhahaha
</s:if>
<s:else>
boo
</s:else>
<s:if test="'foo' not in {'foo','bar'}">
muhahaha
</s:if>
<s:else>
boo
</s:else>
取得一個List的一部分:
? – 所有滿足選擇邏輯的對象
^ - 第一個滿足選擇邏輯的對象
$ - 最后一個滿足選擇邏輯的對象
例如:
person.relatives.{? #this.gender == 'male'}
上述代碼取得這個人(person)所有的男性(this.gender==male)的親戚(relatives)
Lambda 表達式
OGNL支持簡單的Lambda表達式語法,使用這些語法可以建立簡單的lambda函數。
例如:
Fibonacci:
if n==0 return 0;
elseif n==1 return 1;
else return fib(n-2)+fib(n-1);
fib(0) = 0
fib(1) = 1
fib(11) = 89
OGNL的Lambda表達式如何工作呢?
Lambda表達式必須放在方括號內部,#this表示表達式的參數。例如:
<s:property value="#fib =:[#this==0 ? 0 : #this==1 ? 1 : #fib(#this-2)+#fib(#this-1)], #fib(11)" />
#fib =:[#this==0 ? 0 : #this==1 ? 1 : #fib(#this-2)+#fib(#this-1)]定義了一個Lambda表達式,
#fib(11) 調用了這個表達式。
所以上述代碼的輸出為:89
在JSP2.1中#被用作了JSP EL(表達式語言)的特殊記好,所以對OGNL的使用可能導致問題,
一個簡單的方法是禁用JSP2.1的EL特性,這需要修改web.xml文件:
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<el-ignored>true</el-ignored>
</jsp-property-group>
</jsp-config>
4.6節 Tag 語法
代碼示例:
表達式 |
含義 |
<p>Username: ${user.username}</p> |
一個在標準上下文中的JavaBean對象,可以適用Freemarker,Velocity,JSTL EL等(不是OGNL)。 |
<s:textfield name="username"/> |
在Value Stack中的一個username屬性。 |
<s:url id="es" action="Hello"> <s:param name="request_locale"> es </s:param> </s:url> <s:a href="%{es}">Espanol</s:a> |
引用Value Stack中屬性的另外一種方法。 |
<s:property name="#session.user.username" /> |
Session中的user對象的username屬性。 |
<s:select label="FooBar" name="foo" list="#{'username':'trillian', 'username':'zaphod'}" /> |
一個簡單的靜態Map,和put("username","trillian")一樣 |
Struts2在OGNL基礎上的增強
1、值棧(ValueStack)
Struts2將OGNL上下文設置為Struts2中的ActionContext(內部使用的仍然是OgnlContext),并將值棧設為OGNL的根對象。
我們知道,OGNL上下文中的根對象可以直接訪問,不需要使用任何特殊的“標記”,而引用上下文中的其他對象則需要使用“#”來標記。由于值棧是 上下文中的根對象,因此可以直接訪問。那么對于值棧中的對象該如何訪問呢?Struts2提供了一個特殊的OGNLPropertyAccessor,它 可以自動查找棧內的所有對象(從棧頂到棧底),直接找到一個具有你所查找的屬性的對象。也就是說,對于值棧中的任何對象都可以直接訪問,而不需要使用 “#”。
假設值棧中有兩個對象:student和employee,兩個對象都有name屬性,student有學號屬性number,而 employee有薪水屬性salary。employee先入棧,student后入棧,位于棧頂,那么對于表達式name,訪問的就是student 的name屬性,因為student對象位于棧頂;表達式salary,訪問的就是employee的salary屬性。正如你所見,訪問值棧中的對象屬 性或方法,無須指明對象,也不用“#”,就好像值棧中的對象都是OGNL上下文中的根對象一樣。這就是Struts2在OGNL基礎上做出的改進。
2、[N]語法
如上所述,如果想要訪問employee的name屬性,應該如何寫表達式呢?我們可以使用[N].xxx(N是從0開始的整數)這樣的語法來指定從哪一個位置開始向下查找對象的屬性,表達式[1].name訪問的就是employee對象的name屬性。
在使用[N].xxx語法時,要注意位置序號的含義,它并不是表示“獲取棧中索引為N的對象”,而是截取從位置N開始的部分棧。
3、top關鍵字
top用于獲取棧頂的對象,結合[N].xxx語法,我們就可以獲取棧中任意位置的對象。
如:[0].top,[1].top等
4、訪問靜態成員
除了使用標準的OGNL表達式訪問靜態字段和靜態方法外,Struts2還允許你不指定完整的類名,而是通過“vs”前綴來調用保存在棧中的靜態字段和靜態方法。
@vs@FOO_PROPERTY
@vs@someMethod()
@vs1@someMethod()
vs表示ValueStack,如果只有vs,那么將使用棧頂對象的類;如果在vs后面跟上一個數字,那么將使用棧中指定位置處的對象類。
5、值棧中的Action實例
Struts2框架總是把Action實例放在棧頂。因為Action在值棧中,而值棧又是OGNL中的根,所以引用Action的屬性可以省略“#”標記,這也是為什么我們在結果頁面中可以直接訪問Action的屬性的原因。
6、Struts2中的命名對象
Struts2還提供了一些命名對象,這些對象沒有保存在值棧中,而是保存在ActionContext中,因此訪問這些對象需要使用“#”標記。這些命名對象都是Map類型。
parameters
用于訪問請求參數。如:#parameters['id']或#parameters.id,相當于調用了HttpServletRequest對象的getParameter()方法。
注意,parameters本質上是一個使用HttpServletRequest對象中的請求參數構造的Map對象,一量對象被創建(在調用Action實例之前就已經創建好了),它和HttpServletRequest對象就沒有了任何關系。
request
用于訪問請求屬性。如:#request['user']或#request.user,相當于調用了HttpServletRequest對象的getAttribute()方法。
session
用于訪問session屬性。如:#session['user']或#session.user,相當于調用了HttpSession對象的getAttribute()方法。
application
用于訪問application屬性。如:#application['user']或#application.user,相當于調用了ServletContext的getAttribute()方法。
attr
如果PageContext可用,則訪問PageContext,否則依次搜索request、session和application對象。
PS:
在JSP 2.1 下 # 被加入了 JSP EL.中。這可能導致一些應用OGNL#號操作的程序出問題,下面是一種快速修復的方法是在web.xml中加上jsp-config的標簽,這適用于一些支持JSP 2.1 EL表達式的容器,如GlassFish.
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<el-ignored>true</el-ignored>
</jsp-property-group>
</jsp-config>