OGNL的全称是Object Graph Navigation Language(对象图导航语言),它是一种强大的表达式语言,让你通过简单一致的表达式语法来读取和设置Java对象的属性值,调用对象的方法,遍历整个对象的结构图,实现字段类型转换等功能。 为什么使用OGNL 相对于其它的表达式语言,OGNL的功能更为强大,它提供了很多高级而必需的特性,例如强大的类型转换功能、静态或实例方法的执行、跨集合投影,以及动态lambda表达式定义等。 OGNL基础 OGNL表达式的计算都是围绕OGNL上下文来进行的,OGNL上下文实际上就是一个Map对象,由ognl.OgnlContext类(实现了java.util.Map接口)来表示。OGNL上下文可以包含一个或多个JavaBean对象,在这些对象中有一个是特殊的,这个对象就是上下文的根(root)对象。如果在写表达式的时候,没有指定使用上下文中的哪一个对象,那么根对象将被假定为表达式所依据的对象。 在OGNL上下文中,只能有一个根对象,如果你访问根对象,那么在写表达式的时候,直接写对象的属性就可以了;否则,你需要使用“#key”前缀,例如表达式#namager.name。 OGNL表达式 OGNL表达式的基础单元就是导航链,通常简称为链(chain)。最简单的链由下列部分组成: 1、属性名:如name和manager.name; 2、方法调用:如manager.hashCode(),返回manager对象的散列码; 3、数组索引:如emals[0],返回当前对象的邮件列表中的第一个邮件地址。 所有OGNL表达式的计算都是在当前对象的上下文中,一个链简单地使用链中先前链接的结果作为下一步计算的当前对象。我们看如下所示的链: name.toCharArray()[0].numericValue.toString() 这个表达式按照下列的步骤进行计算: 1、获取根对象的name属性; 2、在String结果上调用toCharArray()方法; 3、从char数组中提取第一个字符; 4、从提取的字符对象上行到numericValue属性(这个字符被表示为Character对象,Character类有一个getNumericValue()方法); 5、在Integer对象结果上调用toString()方法。 这个表达式最终结果是最后返回的toString()方法调用返回的字符串。 常量 OGNL支持的所有常量类型: 1、字符串常量: 以单引号或双引号括起来的字符串。如”hello”,’hello’。 不过要注意的是,如果是单个字符的字符串常量,必须使用双引号。 2、字符常量: 以单引号括起来的字符。如’a'。 3、数值常量: 除了Java中的int、long、float和double外,OGNL还让你使用“b”或“B”后缀指定BigDecimal常量,用“h”“H”后缀指定BigInteger常量。 4、布尔常量: true和false。 5、null常量。 操作符 OGNL除了支持所有的Java操作符外,还支持以下几种: 1、逗号, 与C语言中的逗号操作符类似。 2、花括号{} 用于创建列表,元素之间用逗号分隔。 3、in和not in 用于判断一个值是否在集合中。 访问JavaBean的属性 假如有一个employee对象作为OGNL上下文的根对象,那对于下面的表达式: 1、name 对应的java代码是employee.getName(); 2、address.country 对应的java代码是employee.getAddress().getCountry(); 访问静态方法和静态字段 @class@method(args) //调用静态方法 @class@field //调用静态字段 其中class必须给出完整的类名(包括包名),如果省略class,那么默认使用的类是java.util.Math,如: @@min(5,3) @@max(5,3) @@PI 索引访问 OGNL支持多种索引方式的访问。 1、数组和列表索引 在OGNL中,数组和列表可以大致看成是一样的。 如:array[0]、list[0]。表达式:{’zhangsan’,'lisi’,'wangwu’}[1]等。 2、JavaBean的索引属性 要使用索引属性,需要提供两对setter和getter方法,一对用于数组,一对用于数组中的元素。 如:有一个索引属性interest,它的getter和setter如下 public String[] interest; public String[] getInterest(){ return interest;} public void setInterest(String[] interest){ this.interest=interest;} public String getInterest(int i){ return interest[i]} public void setInterest(int i, String newInterest){ interest[i]=newInterest;} 对于表达式interest[2],OGNL可以正确解释这个表达式,调用getInterest(2)方法。如果是设置的情况下,会调用setInterest(2,value)方法。 3、OGNL对象的索引属性 JavaBean的索引属性只能使用整型作为索引,OGNL扩展了索引属性的概念,可以使用任意的对象来作为索引。 对集合进行操作 1、创建集合: 创建列表 使用花括号将元素包含起来,元素之间使用逗号分隔。如{’zhangsan’,'lisi’,'wangwu’} 创建数组 OGNL中创建数组与Java语言中创建数组类似。 创建Map Map使用特殊的语法来创建 #{”key”:value, ……} 如果想指定创建的Map类型,可以在左花括号前指定Map实现类的类名。如: #@java.util.LinkedHashMap@{”key”:”value”,….} Map通过key来访问,如map["key"]或map.key。 2、投影 OGNL提供了一种简单的方式在一个集合中对每一个元素闻调用相同的方法,或者抽取相同的属性,并将结果保存为一个新的集合,称之为投影。 假如employees是一个包含了employee对象的列表,那么 #employees.{name}将返回所有雇员的名字的列表。 在投影期间,使用#this变量来引用迭代中的当前元素。 如:objects.{#this instanceof String? #this: #this.toString()} 3、选择 OGNL提供了一种简单的方式来使用表达式从集合中选择某些元素,并将结果保存到新的集合中,称为选择。 如#employees.{?#this.salary>3000} 将返回薪水大于3000的所有雇员的列表。 #employees.{^#this.salary>3000} 将返回第一个薪水大于3000的雇员的列表。 #employees.{$#this.salary>3000} 将返回最后一个薪水大于3000的雇员的列表。 Struts 2的OGNL OGNL是Struts 2框架的默认表达式语言,增强了Struts 2的数据访问能力,同时简化了代码。 8.2.1 Struts 2的OGNL表达式 标准的OGNL会设定一个根对象(root对象)。假设使用标准OGNL表达式来求值(不使用Struts 2的OGNL表达式),如果OGNL上下文(OgnlContext Map类型)有两个对象:foo对象,在OgnlContext中名称为foo;bar对象,在OgnlContext中名称为bar。同时foo对象被设置为根对象(root)。则利用下面的OGNL表达式求值: // 返回foo.getBlah() #foo.blah //返回bar.getBlah() #bar.blah //返回foo.getBlah() ,因为foo为根对象 blah ★ 说明 ★ 使用OGNL是非常简单的,如果要访问的对象不是根对象,如示例中的bar对象,则需要使用命名空间,用“#”来标识,如“#bar”;如果访问一个根对象,则不用指定命名空间,可以直接访问根对象的属性。 在Struts 2框架中,值栈(Value Stack)就是OGNL的根对象,假设值栈中存在两个对对象实例:Man和Animal,这两个对象实例都有一个name属性,Animal有一个species属性,Man有一个salary属性,假设Animal在值栈的顶部,Man在Animal后面,下面的代码片断会帮助读者更好地理解OGNL表达式: // 调用animal.getSpecies() species // 调用man.getSalary() salary // 调用animal.getName(),因为Animal位于值栈的顶部 name 最后一行示例代码,返回的是animal.getName()返回值,即返回了Animal的name属性,因为Animal是值栈的顶部元素,OGNL将从顶部元素搜索,所以会返回Animal的name属性值。如果要获得Man的name值,则需要如下代码: man.name Struts 2允许在值栈中使用索引,示例代码如下所示: [0].name // 调用animal.getName() [1].name // 调用man.getName() ★ 说明注意 ★ 使用索引,并不是直接获得指定的元素,而是从指定的索引位置搜索。 Struts 2中的OGNL Context是ActionContext,如图8.2所示。 图8.2 Struts 2的OGNL Context结构示意图 ★ 说明 ★ 图8.2只是说明Struts 2的OGNL Context结构,实际上Context还包含其他对象。 由于值栈是Struts 2中OGNL的根对象,如果用户需要访问值栈中的对象,则可以直接通过下面的代码访问值栈中的属性: //获得值栈中的foo属性 ${foo} 如果访问其他Context中的对象,由于不是根对象,在访问时,需要加#前缀。 — application对象:用于访问ServletContext,例如#application.userName或者#application['userName'],相当于调用Servlet的getAttribute("username")。 — session对象:用来访问HttpSession,例如#session.userName或者#session['userName'],相当于调用session.getAttribute("userName")。 — request对象:用来访问HttpServletRequest属性(attribute)的Map,例如#request.userName或者#request['userName'],相当于调用request.get力Attribute ("userName")。 — parameters对象:用于访问HTTP的请求参数,例如#parameters.userName或者#parameters['userName'],相当于调用request.getParameter("username")。 — attr对象:用于按page-request-session-application顺序访问其属性。 OGNL的集合操作 如果需要一个集合元素的时候(例如List对象或者Map对象),可以使用OGNL中同集合相关的表达式。 可以使用如下代码直接生成一个List对象: {e1,e2,e3…} 该OGNL表达式中,直接生成了一个List对象,该List对象中包含3个元素:e1、e2和e3。如果需要更多的元素,可以按照这样的格式定义多个元素,多个元素之间使用逗号隔开。 如下代码可以直接生成一个Map对象: #{key1:value1,key2:value2,…} Map类型的集合对象,使用key-value格式定义,每个key-value元素使用冒号标识,多个元素之间使用逗号隔开。 对于集合类型,OGNL表达式可以使用in和not in两个元素符号。其中,in表达式用来判断某个元素是否在指定的集合对象中;not in判断某个元素是否不在指定的集合对象中,如代码8.3所示。
<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> 除了in和not in之外,OGNL还允许使用某个规则获得集合对象的子集,常用的有以下3个相关操作符。 — ?:获得所有符合逻辑的元素。 — ^:获得符合逻辑的第一个元素。 — $:获得符合逻辑的最后一个元素。 例如代码: person.relatives.{? #this.gender == 'male'} 该代码可以获得person的所有性别为male的relatievs集合。 Lambda表达式 OGNL支持基本的Lambda表达式语法,通过Lambda表达式语法,可以在OGNL中使用一些简单的函数。例如: 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 开发者可以使用Lambda表达式语法: <s:property value="#fib =:[#this==0 ? 0 : #this==1 ? 1 : #fib(#this-2)+#fib(#this-1)], #fib(11)" /> |