转载;
引言:众所周知,在mvc中,数据是在各个层次之间进行流转是一个不争的事实。而这种流转,也就会面临一些困境,这些困境,是由于数据在不同世界中的表现形式不同而造成的:
1. 数据在页面上是一个扁平的,不带数据类型的字符串,无论你的数据结构有多复杂,数据类型有多丰富,到了展示的时候,全都一视同仁的成为字符串在页面上展现出来。
2. 数据在Java世界中可以表现为丰富的数据结构和数据类型,你可以自行定义你喜欢的类,在类与类之间进行继承、嵌套。我们通常会把这种模型称之为复杂的对象树。
此时,如果数据在页面和Java世界中互相流转传递,就会显得不匹配。所以也就引出了几个需要解决的问题:
1. 当数据从View层传递到Controller层时,我们应该保证一个扁平而分散在各处的数据集合能以一定的规则设置到Java世界中的对象树中去。同时,能够聪明的进行由字符串类型到Java中各个类型的转化。
2. 当数据从Controller层传递到View层时,我们应该保证在View层能够以某些简易的规则对对象树进行访问。同时,在一定程度上控制对象树中的数据的显示格式。
如果我们稍微深入一些来思考这个问题,我们就会发现,解决数据由于表现形式的不同而发生流转不匹配的问题对我们来说其实并不陌生。同样的问题会发生在Java世界与数据库世界中,面对这种对象与关系模型的不匹配,我们采用的解决方法是使用ORM框架,例如hibernate,iBatis等等。那么现在,在Web层同样也发生了不匹配,所以我们也需要使用一些工具来帮助我们解决问题。为了解决数据从View层传递到Controller层时的不匹配性,Struts2采纳了XWork的一套完美方案。并且在此的基础上,构建了一个完美的机制,从而比较完美的解决了数据流转中的不匹配性。相信大家看到这一定猜出来了这里的完美方案和完美机制了。对,这就是OGNL方案和OGNLValueStack机制
基本概念
OGNL(Object Graph Navigation Language),是一种表达式语言。使用这种表达式语言,你可以通过某种表达式语法,存取Java对象树中的任意属性、调用Java对象树的方法、同时能够自动实现必要的类型转化。如果我们把表达式看做是一个带有语义的字符串,那么OGNL无疑成为了这个语义字符串与Java对象之间沟通的桥梁。既然OGNL那么强大,那么让我们一起来研究一下他的API,看看如何使用OGNL.
OGNL的API看起来就是两个简单的静态方法:
- public static Object getValue( Object tree, Map context, Object root ) throws OgnlException;
- public static void setValue( Object tree, Map context, Object root, Object value ) throws OgnlException
- public static Object getValue( Object tree, Map context, Object root ) throws OgnlException;
- public static void setValue( Object tree, Map context, Object root, Object value ) throws OgnlException
我们可以看到,简单的API,就已经能够完成对各种对象树的读取和设值工作了。这也体现出OGNL的学习成本非常低。需要特别强调进行区分的,是在针对不同内容进行取值或者设值时,OGNL表达式的不同。Struts2 Reference 写道:
The Framework uses a standard naming context to evaluate OGNL expressions. The top level object dealing with OGNL is a Map (usually referred as a context map or context). OGNL has a notion of there being a root (or default) object within the context. In expression, the properties of the root object can be referenced without any special "marker" notion. References to other objects are marked with a pound sign (#).
针对上面的话,我们可以简单的理解为下面两点:
A) 针对根对象(Root Object)的操作,表达式是自根对象到被访问对象的某个链式操作的字符串表示。
B) 针对上下文环境(Context)的操作,表达式是自上下文环境(Context)到被访问对象的某个链式操作的字符串表示,但是必须在这个字符串的前面加上#符号,以表示与访问根对象的区别。
OGNL三要素:
很多人习惯上把传入OGNL的API的三个参数,称之为OGNL的三要素。OGNL的操作实际上就是围绕着这三个参数而进行的。
看下面一段测试代码:
- Ognl.setValue("department.name", user2, "dev");
- System.out.println(user2.getDepartment().getName());
- Ognl.setValue(Ognl.parseexpression_r("department.name"), context, user2, "otherDev");
- System.out.println(user2.getDepartment().getName());
- Ognl.setValue("department.name", user2, "dev");
- System.out.println(user2.getDepartment().getName());
- Ognl.setValue(Ognl.parseexpression_r("department.name"), context, user2, "otherDev");
- System.out.println(user2.getDepartment().getName());
1. 表达式(Expression)
表达式是整个OGNL的核心,所有的OGNL操作都是针对表达式的解析后进行的。表达式会规定此次OGNL操作到底要干什么。我们可以看到,在上面的测试中,name、department.name等都是表达式,表示取name或者department中的name的值。OGNL支持很多类型的表达式,之后我们会看到更多。
2. 根对象(Root Object)
根对象可以理解为OGNL的操作对象。在表达式规定了“干什么”以后,你还需要指定到底“对谁干”。
在上面的测试代码中,user就是根对象。这就意味着,我们需要对user这个对象去取name这个属性的值(对user这个对象去设置其中的department中的name属性值)。
3. 上下文环境(Context)
有了表达式和根对象,我们实际上已经可以使用OGNL的基本功能。例如,根据表达式对根对象进行取值或者设值工作。不过实际上,在OGNL的内部,所有的操作都会在一个特定的环境中运行,这个环境就是OGNL的上下文环境(Context)。说得再明白一些,就是这个上下文环境(Context),将规定OGNL的操作“在哪里干”。
OGNL的上下文环境是一个Map结构,称之为OgnlContext。上面我们提到的根对象(Root Object),事实上也会被加入到上下文环境中去,并且这将作为一个特殊的变量进行处理,具体就表现为针对根对象(Root Object)的存取操作的表达式是不需要增加#符号进行区分的。
OgnlContext不仅提供了OGNL的运行环境。在这其中,我们还能设置一些自定义的parameter到Context中,以便我们在进行OGNL操作的时候能够方便的使用这些parameter。不过正如我们上面反复强调的,我们在访问这些parameter时,需要使用#作为前缀才能进行。
OGNL表达式实现原理
Struts 2中的OGNL Context实现者为ActionContext,它结构示意图如下:
当Struts2接受一个请求时,会迅速创建ActionContext,ValueStack,action 。然后把action存放进ValueStack,所以action的实例变量可以被OGNL访问。访问上下文(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标签才可以使用。如:
由于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对象: 用于按request > session > application顺序访问其属性(attribute) #attr.userName相当于按顺序在以上三个范围(scope)内读取userName属性,直到找到为止 。
好了,基本概念和原理占时先介绍到这,下篇博客则着重说一下OGNL表达式的基本语法和用法,谢谢大家一直以来的支持。
在上篇博客,我们一起看了《ognl概念和原理详解》,我们大约的知道了ognl的基本实现原理和一些基本概念,这节我们一起来学习一下OGNL表达式的基本语法和基本用法,首先我们一起来看一下OGNL中的#、%和$符号。
一.OGNL中的#、%和$符号
#、%和$符号在OGNL表达式中经常出现,而这三种符号也是开发者不容易掌握和理解的部分。在这里我们简单介绍它们的相应用途。
1.#符号的三种用法
1)访问非根对象属性,例如示例中的#session.msg表达式,由于Struts 2中值栈被视为根对象,所以访问其他非根对象时,需要加#前缀。实际上,#相当于ActionContext. getContext();#session.msg表达式相当于ActionContext.getContext().getSession(). getAttribute("msg") 。
2)用于过滤和投影(projecting)集合,如示例中的persons.{?#this.age>20}。
1.集合的投影(过滤)有以下三种方式:
a.“?#”:投影(过滤)所有符合条件的集合,如:users.{?#this.age > };
b.“^#”:投影(过滤)第一个符合条件的元素,如:users.{^#this.age > };
c.“$#”:投影(过滤)最后一个符合条件的元素,如:users.{$#this.age > } 。
2.“this”表示集合中的元素; 3.投影(过滤)操作返回的是一个集合,可以使用索引取得集合中指定的元素,如: users.{?#this.age > }[0]。
3) 用来构造Map,例如示例中的#{'foo1':'bar1', 'foo2':'bar2'}。
2.%符号
%符号的用途是在标签的属性为字符串类型时,计算OGNL表达式的值。如下面的代码所示:
构造Map
The value of key "foo1" is
不使用%:
使用%:
运行界面如下所示。
he value of key "foo1" is bar1
不使用%:#foobar['foo1']
使用%:bar1
3.$符号
$符号主要有两个方面的用途。
1) 在国际化资源文件中,引用OGNL表达式,例如国际化资源文件中的代码:reg.agerange=国际化资源信息:年龄必须在${min}同${max}之间。
2) 在Struts 2框架的配置文件中引用OGNL表达式,例如下面的代码片断所示:
- <validators>
- <field name="intb">
- <field-validator type="int">
- <param name="min">10</param>
- <param name="max">100</param>
- <message>BAction-test校验:数字必须为${min}为${max}之间!</message>
- </field-validator>
- </field>
- </validators>
- <validators>
- <field name="intb">
- <field-validator type="int">
- <param name="min">10</param>
- <param name="max">100</param>
- <message>BAction-test校验:数字必须为${min}为${max}之间!</message>
- </field-validator>
- </field>
- </validators>
二.我们一起看一下OGNL常用表达式:
1. 当使用OGNL调用静态方法的时候,需要按照如下语法编写表达式:
@package.classname@methodname(parameter)
2. 对于OGNL来说,java.lang.Math是其的默认类,如果调用java.lang.Math的静态方法时,无需指定类的名字,比如:@@min(4, 10);
3. 对于OGNL来说,数组与集合是一样的,都是通过下标索引来去访问的。
获取List: 获取List中的某一个元素(可以使用类似于数组中的下标获取List中的内容): 获取Set: 获取Set中的某一个元素(Set由于没有顺序,所以不能使用下标获取数据):
获取Map: 获取Map中所有的键: 获取Map中所有的值: 获取Map中的某一个元素(可以使用类似于数组中的下标获取List中的内容): 获取List的大小: |
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中成绩及格的对象: 利用选择获取List中成绩及格的对象的username: 利用选择获取List中成绩及格的第一个对象的username: 利用选择获取List中成绩及格的第一个对象的username: 利用选择获取List中成绩及格的最后一个对象的username: 利用选择获取List中成绩及格的第一个对象然后求大小: |
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表达式,那么标签处理类就会将%{}忽略掉。
最后一起用代码说话,简单的看一下ognl操作的示例:
1)上下文环境中使用OGNL
- public static void main(String[] args)
- {
- /* 创建一个上下文Context对象,它是用保存多个对象一个环境 对象*/
- Map context = new HashMap();
- Person person1 = new Person();
- person1.setName("zhangsan");
- Person person2 = new Person();
- person2.setName("lisi");
- Person person3 = new Person();
- person3.setName("wangwu");
- /* person4不放入到上下文环境中*/
- Person person4 = new Person();
- person4.setName("zhaoliu");
- /* 将person1、person2、person3添加到环境中(上下文中)*/
- context.put("person1", person1);
- context.put("person2", person2);
- context.put("person3", person3);
- try
- {
- /* 获取根对象的"name"属性值*/
- Object value = Ognl.getValue("name", context, person2);
- System.out.println("ognl expression \"name\" evaluation is : " + value);
- /* 获取根对象的"name"属性值*/
- Object value2 = Ognl.getValue("#person2.name", context, person2);
- System.out.println("ognl expression \"#person2.name\" evaluation is : " + value2);
- /* 获取person1对象的"name"属性值*/
- Object value3 = Ognl.getValue("#person1.name", context, person2);
- System.out.println("ognl expression \"#person1.name\" evaluation is : " + value3);
- /* 将person4指定为root对象,获取person4对象的"name"属性,注意person4对象不在上下文中*/
- Object value4 = Ognl.getValue("name", context, person4);
- System.out.println("ognl expression \"name\" evaluation is : " + value4);
- /* 将person4指定为root对象,获取person4对象的"name"属性,注意person4对象不在上下文中*/
- Object value5 = Ognl.getValue("#person4.name", context, person4);
- System.out.println("ognl expression \"person4.name\" evaluation is : " + value5);
- /* 获取person4对象的"name"属性,注意person4对象不在上下文中*/
- // Object value6 = Ognl.getValue("#person4.name", context, person2);
- // System.out.println("ognl expression \"#person4.name\" evaluation is : " + value6);
- }
- public static void main(String[] args)
- {
- /* 创建一个上下文Context对象,它是用保存多个对象一个环境 对象*/
- Map context = new HashMap();
- Person person1 = new Person();
- person1.setName("zhangsan");
- Person person2 = new Person();
- person2.setName("lisi");
- Person person3 = new Person();
- person3.setName("wangwu");
- /* person4不放入到上下文环境中*/
- Person person4 = new Person();
- person4.setName("zhaoliu");
- /* 将person1、person2、person3添加到环境中(上下文中)*/
- context.put("person1", person1);
- context.put("person2", person2);
- context.put("person3", person3);
- try
- {
- /* 获取根对象的"name"属性值*/
- Object value = Ognl.getValue("name", context, person2);
- System.out.println("ognl expression \"name\" evaluation is : " + value);
- /* 获取根对象的"name"属性值*/
- Object value2 = Ognl.getValue("#person2.name", context, person2);
- System.out.println("ognl expression \"#person2.name\" evaluation is : " + value2);
- /* 获取person1对象的"name"属性值*/
- Object value3 = Ognl.getValue("#person1.name", context, person2);
- System.out.println("ognl expression \"#person1.name\" evaluation is : " + value3);
- /* 将person4指定为root对象,获取person4对象的"name"属性,注意person4对象不在上下文中*/
- Object value4 = Ognl.getValue("name", context, person4);
- System.out.println("ognl expression \"name\" evaluation is : " + value4);
- /* 将person4指定为root对象,获取person4对象的"name"属性,注意person4对象不在上下文中*/
- Object value5 = Ognl.getValue("#person4.name", context, person4);
- System.out.println("ognl expression \"person4.name\" evaluation is : " + value5);
- /* 获取person4对象的"name"属性,注意person4对象不在上下文中*/
- // Object value6 = Ognl.getValue("#person4.name", context, person2);
- // System.out.println("ognl expression \"#person4.name\" evaluation is : " + value6);
- }
2)使用OGNL调用方法
- public static void main(String[] args)
- {
- /* OGNL提供的一个上下文类,它实现了Map接口*/
- OgnlContext context = new OgnlContext();
- People people1 = new People();
- people1.setName("zhangsan");
- People people2 = new People();
- people2.setName("lisi");
- People people3 = new People();
- people3.setName("wangwu");
- context.put("people1", people1);
- context.put("people2", people2);
- context.put("people3", people3);
- context.setRoot(people1);
- try
- {
- /* 调用 成员方法*/
- Object value = Ognl.getValue("name.length()", context, context.getRoot());
- System.out.println("people1 name length is :" + value);
- Object upperCase = Ognl.getValue("#people2.name.toUpperCase()", context, context.getRoot());
- System.out.println("people2 name upperCase is :" + upperCase);
- Object invokeWithArgs = Ognl.getValue("name.charAt(5)", context, context.getRoot());
- System.out.println("people1 name.charAt(5) is :" + invokeWithArgs);
- /* 调用静态方法*/
- Object min = Ognl.getValue("@java.lang.Math@min(4,10)", context, context.getRoot());
- System.out.println("min(4,10) is :" + min);
- /* 调用静态变量*/
- Object e = Ognl.getValue("@java.lang.Math@E", context, context.getRoot());
- System.out.println("E is :" + e);
- }
- public static void main(String[] args)
- {
- /* OGNL提供的一个上下文类,它实现了Map接口*/
- OgnlContext context = new OgnlContext();
- People people1 = new People();
- people1.setName("zhangsan");
- People people2 = new People();
- people2.setName("lisi");
- People people3 = new People();
- people3.setName("wangwu");
- context.put("people1", people1);
- context.put("people2", people2);
- context.put("people3", people3);
- context.setRoot(people1);
- try
- {
- /* 调用 成员方法*/
- Object value = Ognl.getValue("name.length()", context, context.getRoot());
- System.out.println("people1 name length is :" + value);
- Object upperCase = Ognl.getValue("#people2.name.toUpperCase()", context, context.getRoot());
- System.out.println("people2 name upperCase is :" + upperCase);
- Object invokeWithArgs = Ognl.getValue("name.charAt(5)", context, context.getRoot());
- System.out.println("people1 name.charAt(5) is :" + invokeWithArgs);
- /* 调用静态方法*/
- Object min = Ognl.getValue("@java.lang.Math@min(4,10)", context, context.getRoot());
- System.out.println("min(4,10) is :" + min);
- /* 调用静态变量*/
- Object e = Ognl.getValue("@java.lang.Math@E", context, context.getRoot());
- System.out.println("E is :" + e);
- }
3)使用OGNL操作集合
-
- public static void main(String[] args) throws Exception
- {
- OgnlContext context = new OgnlContext();
- Classroom classroom = new Classroom();
- classroom.getStudents().add("zhangsan");
- classroom.getStudents().add("lisi");
- classroom.getStudents().add("wangwu");
- classroom.getStudents().add("zhaoliu");
- classroom.getStudents().add("qianqi");
- Student student = new Student();
- student.getContactWays().put("homeNumber", "110");
- student.getContactWays().put("companyNumber", "119");
- student.getContactWays().put("mobilePhone", "112");
- context.put("classroom", classroom);
- context.put("student", student);
- context.setRoot(classroom);
- /* 获得classroom的students集合*/
- Object collection = Ognl.getValue("students", context, context.getRoot());
- System.out.println("students collection is :" + collection);
- /* 获得classroom的students集合*/
- Object firstStudent = Ognl.getValue("students[0]", context, context.getRoot());
- System.out.println("first student is : " + firstStudent);
- /* 调用集合的方法*/
- Object size = Ognl.getValue("students.size()", context, context.getRoot());
- System.out.println("students collection size is :" + size);
- System.out.println("--------------------------飘逸的分割线--------------------------");
- Object mapCollection = Ognl.getValue("#student.contactWays", context, context.getRoot());
- System.out.println("mapCollection is :" + mapCollection);
- Object firstElement = Ognl.getValue("#student.contactWays['homeNumber']", context, context.getRoot());
- System.out.println("the first element of contactWays is :" + firstElement);
- System.out.println("--------------------------飘逸的分割线--------------------------");
- /* 创建集合*/
- Object createCollection = Ognl.getValue("{'aa','bb','cc','dd'}", context, context.getRoot());
- System.out.println(createCollection);
- /* 创建Map集合*/
- Object createMapCollection = Ognl.getValue("#{'key1':'value1','key2':'value2'}", context, context.getRoot());
- System.out.println(createMapCollection);
- }
- }