Struts2的OGNL表达式详解

1.Struts 2支持以下几种表达式语言:

  1. OGNL(Object-Graph Navigation Language),可以方便地操作对象属性的开源表达式语言;
  2. JSTL(JSP Standard Tag Library),JSP 2.0集成的标准的表达式语言;
  3. Groovy,基于Java平台的动态语言,它具有时下比较流行的动态语言(如Python、Ruby和Smarttalk等)的一些起特性;
  4. Velocity,严格来说不是表达式语言,它是一种基于Java的模板匹配引擎,具说其性能要比JSP好。

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. 操作集合对象

2.在使用OGNL之前,请确保将OGNL必须的jar包添加到WEB-INF/lib包中,必须的包名称是:ognl-x.x.xx.jar和javassist-x.x.xx.jar;


3.OGNL的常见功能有:设置和获取java对象属性,访问静态和非静态方法,集合的过滤和投影,lambda表达式等。

3.1设置和获取java对象属性

struts2的Stact Context 除了包含ValueStack根,还包含parameters,request,session,application,attr等命名对象,其中ValueStack是StackContext的根对象,对这些对象的访问操作需要在对象前加“”#“”前缀,汇总如下图:

parameters包含当前HTTP请求参数的Map#parameters.id[0]作用相当于request.getParameter("id")
request包含当前HTTP请求属性的Map#request.userName作用相当于request.getAttribute("userName")
session包含当前HttpSession属性的Map#session.userNmae作用相当于session.getAttribute("userName")
application包含当前ServletContext属性的Map#application.userName作用相当于application.getAttribute("userName")
attr按request>session>application的顺序访问其属性#attr.userName作用相当于按顺序在以上三个范围(scope)内读取userName属性,直到找到该属性

而对于ValueStack,这个就要介绍后面所提到的Ognl类和OgnlContext类了。这两个类的作用是:
Ognl:用来解析和解释执行Ognl表达式;
OgnlContext类:这个类为Ognl表达式提供了一个执行环境,我们可以通过这个类提供的put(key,opj)方法向OgnlContext环境中放各种各样的对象。OgnlContext的对象有两种,其中一种叫做root对象,这个对象有且值能有1个,可以通过调用OgnlContext.setRoot(obj)来设置。第二种就是普通对象,这个数量不受限制。
下面看一段代码,
public class OgnlTest {  
  
    public static void main(String[] args) throws OgnlException {  
        // 新建一个部门对象并设置部门名称  ,部门类包含部门名称name 1个属性
        SlDept dept = new SlDept();  
        dept.setName("软件开发部");  
  
        // 新建一个员工对象并设置员工姓名,员工类包含slDept部门属性和name属性两个属性  
        SlEmployee emp = new SlEmployee();  
        emp.setName("李四");  
        emp.setSlDept(dept);  
  
        // 构建一个OgnlContext对象  
        OgnlContext context = new OgnlContext();  
  
        // 将上述部门和员工对象放入Ognl上下文环境中  
        context.put("dept", dept);  
        context.put("emp", emp);  
  
        // 将员工设置为根对象  
        context.setRoot(emp);  
  
        // 从普通对象中直接获取部门名称  
        Object expression = Ognl.parseExpression("#dept.name");  
        Object deptName = Ognl.getValue(expression, context, context.getRoot());  
        System.out.println(deptName);  
        System.out.println("-------------------------------------------");  
        // 间接获取部门名称  
        expression = Ognl.parseExpression("#emp.slDept.name");  
        deptName = Ognl.getValue(expression, context, context.getRoot());  
        System.out.println(deptName);  
        System.out.println("-------------------------------------------");  
        // 从根对象中直接获取部门名称  
        expression = Ognl.parseExpression("slDept.name");  
        deptName = Ognl.getValue(expression, context, context.getRoot());  
        System.out.println(deptName);  
    }  
  
}  


上段代码我们可以看出,设置跟对象的目的是为了简化语句长度。对于#emp.slDept.name,可以用#slDept.name代替。对于根对象中的属性,可以直接访问,比如访问员工的名字,可以直接用name来代替。原理是这样的,如果Ognl在解析表达式的时候发现表达式开头带有"#",会去普通对象中,去寻找,如果没有"#",则会默认去根对象中去寻找,由于根对象只有一个,所以只需要属性名字。

3.2 访问静态方法和非静态方法

OGNL访问非静态方法比较简单,只需要在后面继续增加一个.methodName即可。访问静态方法需要@符号,形如@package.classname@methodname(parameter) 。如下所示:

package com.ognl.test;  
  
import com.ognl.domain.SlDept;  
import com.ognl.domain.SlEmployee;  
  
import ognl.Ognl;  
import ognl.OgnlContext;  
import ognl.OgnlException;  
  
public class OgnlTest2 {  
  
    public static void main(String[] args) throws OgnlException {  
        // 新建一个部门对象并设置部门名称  
        SlDept dept = new SlDept();  
        dept.setName("上海-软件技术支持部");  
  
        // 新建一个员工对象并设置员工姓名  
        SlEmployee emp = new SlEmployee();  
        emp.setName("李小龙");  
        emp.setSlDept(dept);  
        emp.setAge("22");  
        // 构建一个OgnlContext对象  
        OgnlContext context = new OgnlContext();  
  
        // 将上述部门和员工对象放入Ognl上下文环境中  
        context.put("dept", dept);  
        context.put("emp", emp);  
  
        // 将员工设置为跟对象  
        context.setRoot(emp);  
  
        // 从根对象中直接获取部门名称长度,非静态方法  name是个String,所以可以获得其长度
        Object expression = Ognl.parseExpression("slDept.name.length()");  
        Object length = Ognl.getValue(expression, context, context.getRoot());  
        System.out.println(length);  
          
        // 在Ognl表达式中使用静态方法  
        expression = Ognl.parseExpression("@java.lang.Integer@valueOf(age)");  
        length = Ognl.getValue(expression, context, context.getRoot());  
        System.out.println(length);  
          
    }  
  
}  

3.3OGNL的集合、数组、Map操作


public class OgnlTest3 {  
  
    public static void main(String[] args) throws OgnlException {  
  
        OgnlContext context = new OgnlContext();  
  
        // 通过Ognl表达式构建一个LinkedList对象,这注意:一定是包名+类名的形式  
        Object list = Ognl.parseExpression("new java.util.LinkedList()");  
        Object obj = Ognl.getValue(list, context, context.getRoot());  
        System.out.println(obj);  
        System.out.println("----------------------------");  
  
        // 在Ognl中提供了一种类似数组索引的方式访问集合指定位置的元素  
        // 下述例子直接构建了一个包含aa, bb, cc, dd四个元素的集合,然后访问集合中的第三个元素  
        Object object15 = Ognl.getValue("{'aa', 'bb', 'cc', 'dd'}[2]", context, context.getRoot());  
        System.out.println(object15);  
        System.out.println("----------------------------");  
          
        // 处理数组类型  
        String[] strs = new String[] { "aa", "bb", "cc" };  
        context.put("strs", strs);  
        System.out.println(Ognl.getValue("#strs[1]", context, context.getRoot()));  
        System.out.println("----------------------------");  
          
        // 处理集合类型  
        List<String> words = new ArrayList<String>();  
        words.add("hello");  
        words.add("world");  
        words.add("hello world");  
        context.put("words", words);  
        System.out.println(Ognl.getValue("#words[0]", context, context.getRoot()));  
        System.out.println("----------------------------");  
          
        // 处理Map类型,注意的是为了与集合区分开,在大括号前面加"#"  
        System.out.println(  
                Ognl.getValue("#{'key1': 'value1', 'key2': 'value2', 'key3': 'value3', 'key4': 'value4'}['key3']",  
                        context, context.getRoot()));  
  
    }  
}  

如上述代码,处理集合,数组,map时,都可以用#list|array|map [key]的形式获得对应的值。OGNL还能对数组,集合,map进行过滤,过滤表达式的写法是list|array|map.{?|^|$ #condition},其中?表示满足condition的所有情况,^表示满足condition的第一个情况,$表示满足condition的最后一个情况。也可以进行投影,投影的意思是将原集合中所有对象的某个属性抽取出来,单独构成一个新的集合对象返回,基础语法为 :collection.{expression};例子如下所示:
public class Student {  
      
    private String name;  
      
    private int age;  
      
    private double height;  
  
    public Student() {  
        super();  
    }  
  
    public Student(String name, int age, double height) {  
        super();  
        this.name = name;  
        this.age = age;  
        this.height = height;  
    }  
  
    public String getName() {  
        return name;  
    }  
  
    public void setName(String name) {  
        this.name = name;  
    }  
  
    public int getAge() {  
        return age;  
    }  
  
    public void setAge(int age) {  
        this.age = age;  
    }  
  
    public double getHeight() {  
        return height;  
    }  
  
    public void setHeight(double height) {  
        this.height = height;  
    }  
  
    @Override  
    public String toString() {  
        return "Student [name=" + name + ", age=" + age + ", height=" + height + "]";  
    }  
}  

public class OgnlTest4 {  
  
    public static void main(String[] args) throws OgnlException {  
  
        Student s1 = new Student("Tom", 22, 170.3);  
        Student s2 = new Student("Jack", 21, 176.2);  
        Student s3 = new Student("Tomas", 23, 180.1);  
        Student s4 = new Student("Lucy", 20, 163.3);  
  
        List<Student> stus = new ArrayList<Student>();  
        Collections.addAll(stus, s1, s2, s3, s4);  
        // 新建OgnlContext对象  
        OgnlContext context = new OgnlContext();  
        context.put("stus", stus);  
  
        // 过滤(filtering),collection.{? expression}  
        // ?利用过滤获取身高在175以上的所有学生集合  
        // 输出结果:[Student [name=Jack, age=21, height=176.2], Student [name=Tomas, age=23, height=180.1]]  
        System.out.println(Ognl.getValue("#stus.{? #this.height > 175.0}", context, context.getRoot()));  
  
        // 过滤(filtering),collection.{^ expression}  
        //^ 利用过滤获取身高在175以上的所有学生集合中第一个元素  
        // 输出结果:[Student [name=Jack, age=21, height=176.2]]  
        System.out.println(Ognl.getValue("#stus.{^ #this.height > 175.0}", context, context.getRoot()));  
  
        // 过滤(filtering),collection.{$ expression}  
        // $利用过滤获取身高在175以上的所有学生集合的最后一个元素  
        // 输出结果:[Student [name=Tomas, age=23, height=180.1]]  
        System.out.println(Ognl.getValue("#stus.{$ #this.height > 175.0}", context, context.getRoot()));  
          
        // 投影(projection), collection. {expression}  
        // 获取集合中的所有学生的姓名  
        // 输出结果:[Tom, Jack, Tomas, Lucy]  
        System.out.println(Ognl.getValue("#stus.{name}", context, context.getRoot()));  
    }  
  
}  


4.OGNL在视图页面的应用(如JSP等)

4.1.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'},map可以在jsp中直接用这个创建

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表达式,例如下面的代码片断所示:

  1. <validators>  
  2.   
  3.     <field name="intb">  
  4.   
  5.             <field-validator type="int">  
  6.   
  7.             <param name="min">10</param>  
  8.   
  9.             <param name="max">100</param>  
  10.   
  11.             <message>BAction-test校验:数字必须为${min}为${max}之间!</message>  
  12.   
  13.         </field-validator>  
  14.   
  15.     </field>  
  16.   
  17. </validators>  

3)在jsp的标签中直接使用之前配置的参数或属性,比如:


<s:set name="age" value="61"/> 
<s:if test="${age > 60}"> 
 老年人 


二.我们一起看一下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表达式,那么标签处理类就会将%{}忽略掉。 


总结:1.一般的获得值的方式用#方式操作即可,无论这个你要获得的值是在action里面加进去的,还是在jsp页面加到StatckContext里去的。只有标签里面的语言是字符串的时候,比如<s:url>标签,这个标签需要结合%来操作。
2.对于静态方法的操作,需要使用@,注意默认静态根是java.lang.Math;
3.对于过滤和筛选,这个的话只能死记硬背了。#collection.{?|^|$expression},如前面的例子#student.{?this.height>170};


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值