Struts2的OGNL

OGNL的全称是Object Graph Navigation Language(对象图导航语言),它是一种强大的表达式语言,让你通过简单一致的表达式语法来读取和设置Java对象的属性值,调用对象的方法,遍历整个对象的结构图,实现字段类型转换等功能。

为什么使用OGNL
  相对于其它的表达式语言,OGNL的功能更为强大,它提供了很多高级而必需的特性,例如强大的类型转换功能、静态或实例方法的执行、跨集合投影,以及动态lambda表达式定义等。
OGNL基础
  OGNL表达式的计算都是围绕OGNL上下文来进行的,OGNL上下文实际上就是一个Map对象,由ognl.OgnlContext类(实现了java.util.Map接口)来表示。OGNL上下文可以包含一个或多个JavaBean对象,在这些对象中有一个是特殊的,这个对象就是上下文的根(root)对象。如果在写表达式的时候,没有指定使用上下文中的哪一个对象,那么根对象将被假定为表达式所依据的对象。
  下面我们给出一个例子程序,看看在OGNL中如何根据上下文来计算表达式的值。
Employee.java文件 
package com.oyl.example; 
public class Employee{ 
     private String name; 
     public String getName(){ 
          return name; 
     } 
     public void setName(String name){ 
          this.name=name; 
     } 
} 
Manager.java文件 
package com.oyl.example; 
public class Manager{ 
     private String name; 
     public String getName(){ 
          return name; 
     } 
     public void setName(String name){ 
          this.name=name; 
     } 
} 
OgnlExpression.java 
package com.oyl.example; 
import ognl.Ognl; 
import ognl.OgnlContext; 
import ognl.OgnlException; 
public class OgnlExpression{ 
     private Object expression; 
     public OgnlExpression(String expressionString) throws OgnlException{ 
          expression=Ognl.parseExpression(expressionString); 
     } 
     public Object getExpression(){ 
          return expression; 
     } 
     public Object getValue(OgnlContext context, Object rootObject)throws OgnlException{ 
          return Ognl.getValue(getExpression(), context, rootObject); 
     } 
     public void setValue(OgnlContext context, Object rootObject, Object value) throws OgnlException{ 
          Ognl.setValue(getExpression(), context,rootObject, value); 
     } 
     public static void main(String[] args){ 
          Employee employee=new Employee(); 
          employee.setName("employee"); 
          Manager manager=new Manager(); 
          manager.setName("manager"); 
          //创建OGNL上下文 
          OgnlContext context=new OgnlContext(); 
          //将employee对象和manager对象放到OGNL上下文中 
          context.put("employee",employee); 
          context.put("manager",manager); 
          //将employee对象设置为OGNL上下文的根 
          context.setRoot(employee); 

          //表达式name,将执行employee.getName(),因为employee对象是根对象 
          OgnlExpression exp=new OgnlExpression("name"); 
          System.out.println(exp.getValue(context, context.getRoot())); 

          //表达式#employee.name,将执行employee.getName() 
          exp=new OgnlExpression("#employee.name"); 
          System.out.println(exp.getValue(context, context.getRoot())); 

          //表达式#manager.name,将执行manager.getName() 
          //如果你访问的不是OGNL上下文中的根对象,那么必须在前面加上一个名称空问,例如#manager 
          exp=new OgnlExpression("#manager.name"); 
          System.out.println(exp.getValue(context, context.getRoot())); 
     } 
} 

  在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的雇员的列表。
lambda表达式
  lambda表达式的语法是:   :[...]。OGNL中的lambda表达式只能使用一个参数,这个参数通过#this引用。
  如:
   #fact= :[ #this<=1 ? 1 : #this* #fact ( #this-1) ], #fact(30)
   #fib= :[#this==0 ? 0 : #this==1 ? 1 : #fib(#this-2)+#fib(#this-1)], #fib(11)

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对象。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章