(九)Struts2进阶之OGNL表达式第一弹

这两天一直想写OGNL的总结,但发现下不了笔。今天还是咬牙开始写。

OGNL是Object-Graph Navigation Language的缩写,它是一种功能强大的表达式语言,通过它简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。它使用相同的表达式去存取对象的属性。 ------百度百科

OGNL我们看到最多的就是和Struts2的标签结合使用,但其实OGNL离开了Struts2也是可以的,只是用在Struts2中,就必须和标签库结合才能使用。

这篇文章就先讲讲OGNL不结合Struts2的一些用法,下篇文章再讲OGNL在Struts2中的用法。

1.使用OGNL前的准备工作

要使用OGNL,得导入相应的jar包。


需要带的jar包

2.OgnlContext类和Ognl类的介绍

在类中使用OGNL表达式,和两个类息息相关,分别是OgnlContext类和Ognl类。

Ognl类:This class provides static methods for parsing and interpreting OGNL expressions.根据官方解释,这个类是提供一些静态方法去解析表达式。

OgnlContext:This class defines the execution context for an OGNL expression.该类定义OGNL表达式的执行上下文。

public class OgnlContext extends Object implements Map {}

OgnlContext类实现了Map接口,所以它也是一个Map,可以通过put方法往该上下文环境中放元素。该上下文环境中,有两种对象:根对象和普通对象。我们可以使用它的setRoot方法设置根对象。根对象只能有一个,而普通对象则可以有多个。

3.Ognl获取普通对象和根对象的方法

在上下文环境中,有根对象和普通的对象,两者的获取方式有所不同:获取根对象的属性值,可以直接使用属性名作为表达式,也可以使用#对象名.属性名的方式;获取普通对象的属性值,则必须使用#对象名.属性名的方式获取。下面举例说明。

新建一个School类,有学校名称和老师的集合两个属性

package com.codeliu.ognl;

import java.util.List;

public class School {
    private String name;
    private List<Teacher> teachers;
    
    public School() {}
    
    public School(String name, List<Teacher> teachers) {
        super();
        this.name = name;
        this.teachers = teachers;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public List<Teacher> getTeachers() {
        return teachers;
    }
    public void setTeachers(List<Teacher> teachers) {
        this.teachers = teachers;
    }
    
}

新建一个Teacher类,有姓名、年龄、性别三个属性

package com.codeliu.ognl;

public class Teacher {
    private String name;
    private String gender;
    private int age;
    public Teacher(String name, String gender, int age) {
        super();
        this.name = name;
        this.gender = gender;
        this.age = age;
    }
    public Teacher() {}
    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 String getGender() {
        return gender;
    }
    public void setGender(String gender) {
        this.gender = gender;
    }
    
}

新建一个测试类Test

public class Test {
    public static void main(String[] args) throws OgnlException {
        // 定义一个老师对象
        Teacher t1 = new Teacher("CodeTiger", "男", 22);
        List<Teacher> lists = new ArrayList<Teacher>();
        lists.add(t1);
        // 定义一个学校
        School s = new School("NJUPT", lists);
        // 创建一个OgnlContext实例对象
        OgnlContext context = new OgnlContext();
        // 把学校和老师放入上下文环境中
        context.put("t1", t1);
        context.put("s", s);
        // 设置学校为根对象
        context.setRoot(s);
        // 会覆盖前面的根对象
        // context.setRoot(t1);
        
        // 解析表达式
        // Object expression = Ognl.parseExpression("#s.name");
        Object expression = Ognl.parseExpression("name");
        // Object expression = Ognl.parseExpression("s.name"); // 出错
        // 直接获取根对象的信息,使用#获取上下文中的属性值时,必须使用带Map context参数getValue方法,指定上下文环境
        // 如果是获取根对象,没有使用#,则可以使用不带Map context参数getValue方法,我们也可以使用#获取根对象属性值
        Object result = Ognl.getValue(expression, context, context.getRoot());
        System.out.println(result);
        
        // 获取普通对象的属性
        expression = Ognl.parseExpression("#t1.gender");
        // 此时必须指定上下文环境,不然找不到
        result = Ognl.getValue(expression, context, context.getRoot());
        System.out.println(result);
        
        // 通过根对象获取普通对象的属性
        // expression = Ognl.parseExpression("teachers[0].name");
        expression = Ognl.parseExpression("#s.teachers[0].name");
        // expression = Ognl.parseExpression("s.teachers[0].name"); // Exception in thread "main" ognl.NoSuchPropertyException: com.codeliu.ognl.School.s
        result = Ognl.getValue(expression, context, context.getRoot());
        System.out.println(result);
    }
}

运行输出

NJUPT
男
CodeTiger

以上有几个地方要注意:
1.根对象只能有一个,当你尝试设置多个的时候,后面的会覆盖前面的。
2.访问根对象中的属性,可以直接使用属性名,或者#对象名.属性名,但不能使用对象名.属性名,不然它会认为这是根对象中的一个属性叫对象名,然后访问这个属性的名叫属性名的属性值。(说的我都懵了)
比如我上面获取学习的名称,使用下面的表达式

Object expression = Ognl.parseExpression("s.name"); // 出错

则会出现下面的错误

Exception in thread "main" ognl.NoSuchPropertyException: com.codeliu.ognl.School.s

提示你找不到School类中的s属性,凭你debug多年的经验,应该一眼就能看出问题所在。
3.不使用#获取根对象中的属性值时, 使用getValue方法可以不指定上下文环境,因为第三个参数已经得到了根对象,它指定去根对象中找。如果获取普通对象的属性,则必须在getValue方法中指定上下文环境,因为你不知道它找不到。
4.通过根对象间接获取普通对象的属性时,比如List集合,使用下标去获取。下面会详细讲。

4.Ognl访问非静态方法、静态方法、静态字段的方法

Ognl不但可以访问我们自己定义的类的属性和方法,还可以访问Java API中的静态方法和静态字段。看下面的代码

        // 直接调用根对象中School的getName方 法获取学校名
        // expression = Ognl.parseExpression("getName()");
        expression = Ognl.parseExpression("#s.getName()");
        result = Ognl.getValue(expression, context, context.getRoot());
        System.out.println(result);
        
        // 调用普通对象中的非静态方法
        // expression = Ognl.parseExpression("#t1.getName()");
        expression = Ognl.parseExpression("#t1.getName().length()");
        result = Ognl.getValue(expression, context, context.getRoot());
        System.out.println(result);
        
        // 调用根对象中的静态方法
        expression = Ognl.parseExpression("getTeacherName()");
        result = Ognl.getValue(expression, context, context.getRoot());
        System.out.println(result);
        // 调用API中的静态方法
        expression = Ognl.parseExpression("@java.lang.Math@floor(4.5)");
        result = Ognl.getValue(expression, context, context.getRoot());
        System.out.println(result);
        
        // 调用API中的静态属性
        expression = Ognl.parseExpression("@java.lang.Math@PI");
        result = Ognl.getValue(expression, context, context.getRoot());
        System.out.println(result);

我特地在School类中增加了一个静态方法获取老师集合中第一个老师的姓名,然后使用Ognl调用它。

输出结果如下

NJUPT
9
CodeTiger
4.0
3.141592653589793

5.Ognl获取数组、集合、Map中元素的方法

Ognl表达式可以创建实例对象,以及获取实例对象中的属性,下面我们看看怎么通过Ognl创建一个集合、map以及通过Ognl获取里面的元素。

public class Test2 {
    public static void main(String[] args) throws OgnlException {
        OgnlContext context = new OgnlContext();
        // 通过Ognl可以创建java的实例对象,只有是类的完整路径
        Object expression = Ognl.parseExpression("new java.util.ArrayList()");
        Object result = Ognl.getValue(expression, context, context.getRoot());
        System.out.println(result);
        
        // 通过Ognl可以创建一个初始化的List
        expression = Ognl.parseExpression("{'a', 'b', 'c', 'd'}");
        result = Ognl.getValue(expression, context, context.getRoot());
        System.out.println(result);
        
        // 通过Ognl可以创建一个初始化的Map,注意此时得加上#符号
        expression = Ognl.parseExpression("#{'a':'aa', 'b':'bb', 'c':'cc', 'd':'dd'}");
        // 创建指定类型的Map
        // expression = Ognl.parseExpression("#@java.util.TreeMap@{'a':'aa', 'b':'bb', 'c':'cc', 'd':'dd'}");
        result = Ognl.getValue(expression, context, context.getRoot());
        System.out.println(result);
        
        // 通过Ognl访问数组中的元素
        String[] name1 = {"liu", "xu"};
        context.put("name1", name1);
        // 直接通过数组名+下标
        expression = Ognl.parseExpression("#name1[1]");
        result = Ognl.getValue(expression, context, context.getRoot());
        System.out.println(result);
        
        // 通过Ognl访问集合中的元素
        List<String>  name2 = new ArrayList<String>();
        Collections.addAll(name2, name1);
        context.put("name2", name2);
        // 直接通过集合名+下标
        expression = Ognl.parseExpression("#name2[0]");
        result = Ognl.getValue(expression, context, context.getRoot());
        System.out.println(result);
        
        // 通过Ognl访问Map中的元素
        Map<Integer, String> name3 = new HashMap<Integer, String>();
        name3.put(1, "liu");
        name3.put(2, "xu");
        context.put("name3", name3);
        // 直接通过map名+key
        expression = Ognl.parseExpression("#name3[1]");
        result = Ognl.getValue(expression, context, context.getRoot());
        System.out.println(result);
    }
}

输出

[]
[a, b, c, d]
{a=aa, b=bb, c=cc, d=dd}
xu
liu
liu

注意点
1.创建集合不用加#,创建map要加#
2.创建类的一个对象,要使用类的完整路径
3.要创建带有初始化值的指定类型的List或Map,可以这样#@java.util.TreeMap@{'key':'value','key':'value',......}

6.Ognl中的投影和过滤

无论投影还是过滤,都是针对于数组、集合和Map而言。

投影:把集合中所有对象的某个属性抽出来,单独构成一个新的集合对象。语法如下

collection.{expression}

过滤:将满足条件的对象,构成一个新的集合返回。语法如下

collection.{?|^|$ expression}

上面?^$的含义如下
?:获得所有符合逻辑的元素。
^:获得符合逻辑的第一个元素。
$:获得符合逻辑的最后一个元素。

了解了上面的,我们就看看下面的代码

public class Test3 {
    public static void main(String[] args) throws OgnlException {
        Teacher t1 = new Teacher("liu", "男", 22);
        Teacher t2 = new Teacher("xu", "女", 22);
        Teacher t3 = new Teacher("qian", "男", 30);
        Teacher t4 = new Teacher("li", "女", 35);
        
        List<Teacher> lists = new ArrayList<Teacher>();
        Collections.addAll(lists, new Teacher[] {t1, t2, t3, t4});
        
        OgnlContext context = new OgnlContext();
        context.put("teachers", lists);
        // 把所有老师的名字拿出来,投影
        Object expression = Ognl.parseExpression("#teachers.{name}");
        Object result = Ognl.getValue(expression, context, context.getRoot());
        System.out.println(result);
        
        // 把年龄在20-30之间的所有老师拿出来,过滤
        expression = Ognl.parseExpression("#teachers.{? #this.age > 20 && #this.age < 30}");
        result = Ognl.getValue(expression, context, context.getRoot());
        System.out.println(result);
        
        // 把年龄在20-30之间的第一个老师拿出来,过滤
        expression = Ognl.parseExpression("#teachers.{^ #this.age > 20 && #this.age < 30}");
        result = Ognl.getValue(expression, context, context.getRoot());
        System.out.println(result);
        
        // 把年龄在20-30之间的最后一个老师拿出来,过滤
        expression = Ognl.parseExpression("#teachers.{$ #this.age > 20 && #this.age < 30}");
        result = Ognl.getValue(expression, context, context.getRoot());
        System.out.println(result);
    }
}

输出

[liu, xu, qian, li]
[Teacher [name=liu, gender=男, age=22], Teacher [name=xu, gender=女, age=22]]
[Teacher [name=liu, gender=男, age=22]]
[Teacher [name=xu, gender=女, age=22]]

这个没啥好讲的,记住语法就行了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容