OGNL简介
OGNL的全称是ObjectGraph Navigation Language(对象图导航语言),它是一种强大的表达式语言,让你通过简单一致的表达式语法来读取和设置Java对象的属性值,调用对象的方法,遍历整个对象的结构图,实现字段类型转换等功能。
OGNL先在WebWork项目中得到应用,也是Struts 2框架视图默认的表达式语言,可以说,OGNL表达式是Struts2框架的特点之一。
OGNL表达式的计算都是围绕OGNL上下文来进行的,OGNL上下文实际上就是一个Map对象。
OGNL上下文可以包含一个或多个JavaBean对象,在这些对象中有一个是特殊的,这个对象就是上下文的
根
(root)
对象。如果在写表达式的时候,没有指定使用上下文中的哪一个对象,那么根对象将被假定为表达式所依据的对象
OGNL的优势
OGNL是将视图元素(例如textfield、combobox等)同模型对象绑定在一起的一种语言(数据绑定)。
使用OGNL的类型转换功能,会使类型转换变得更加简单(例如将一个字符串类型转换为一个整数类型)。
可以将Web页面元素同后台服务器业务对象对应起来。
比JSTL(JSP标准标签库)表达更加丰富
OGNL基本语法
基本的OGNL语法是十分简单的,当然OGNL支持丰富的表达式。例如有一个man对象,该对象有一个name属性,那么使用OGNL来获得该name属性可以使用如下表达式:
–man.name
OGNL表达式的基础单元称为导航链,简称为链。一个最简单的链由如下部分组成。
— 属性名称:如上述示例中的name。
— 方法调用:hashCode()返回当前对象的hashcode。
— 数组元素:listeners[0]返回当前对象的监听器列表中第 一个元素。
Root(根对象):所谓对象图,即以任意一个对象为根,通过OGNL可以访问与这个对象关联的其它对象,该对象就称为根对象(root)
OGNL基础语法—导航链
OGNL表达式基于OGNL上下文中的当前对象,一个“链”将使用上一个“链”的处理结果,开发者可以任意扩展该链的长度,OGNL没有限制。
例如,一个OGNL表达式如下:
user.profile.address[0].detailAddr
该表达式将按照如下步骤求值。
(1)获得OGNL Context中根对象(root对象)的user对象。
(2)调用getProfile()方法,返回一个Profile类型对象。
(3)获得该Profile对象的第一个地址属性对象。
(4)获得该Address对象的detailAddr属性
Struts 2的OGNL
OGNL是Struts 2框架的默认表达式语言,增强了Struts 2的数据访问能力,同时简化了代码。
Struts 2中的OGNL Context实现者为ActionContext
当Struts2接受一个请求时,会迅速创建ActionContext,ValueStack,action 。然后把action存放进ValueStack,所以action的实例变量可以被OGNL访问。
Struts2中的OGNL上下文
struts2对OGNL上下文的概念又做了进一步扩充,在struts2中,OGNL上下文通常如下所示:
|-- application
|
|-- session
|
context map---|--OgnlValueStack(root) [action,... ]
|
|-- request
|
|--parameters
|
|-- attr
在struts2中,根对象不是我们通常的一个对象,可以是一组对象。我们可以push新的对象到值栈中(压栈),也可以弹出值栈的栈顶对象(pop出栈)。
ValueStack实际上就是对OGNL的封装,OGNL主要的功能就是赋值与取值,Struts2正是通过ValueStack来进行赋值与取值的!
Struts2 值栈(valueStack)
使用OGNL是非常简单的,如果要访问的对象不是根对象,则需要使用命名空间,用“#”来标识,如“#request”;如果访问一个根对象,则不用指定命名空间,可以直接访问根对象的属性。
在Struts 2框架中, Struts2将OGNL上下文设置为Struts2中的ActionContext,并将值栈设为OGNL的根对象。
值栈。也就是一组对象的堆栈。也就是说,在struts2中,根对象不是我们通常的一个对象,而是一组对象。我们可以push新的对象到值栈中,也可以弹出值栈的栈顶对象
Struts2提供了一个特殊的OGNLPropertyAccessor,它可以自动查找栈内的所有对象(从栈顶到栈底),直接找到一个具有你所查找的属性的对象。也就是说,对于值栈中的任何对象都可以直接访问,而不需要使用“#”。
OGNL不支持多个root对象,而struts2能够支持多个root对象
在Struts2中,一个请求在最终到达Action的方法之前,Action对象本身会被压入ValueStack(实际上就是放到ValueStack的CompoundRoot中),所以Action对象是Action Context中的第一个元素
细说valueStack
ValueStack(值栈)贯穿整个 Action 的生命周期每个 Action 类的对象实例都拥有一个
ValueStack 对象. 相当于一个数据的中转站. 在其中保存当前Action 对象和其他相关对象.
Struts 框架把 ValueStack 对象保存在名为 “struts.valueStack” 的请求属性(request)中
当Struts2接受一个请求时,会迅速创建ActionContext,ValueStack,action 。然后把action存放进ValueStack,所以action的实例变量可以被OGNL访问。
Value Stack举例
假设值栈中有两个对象:student和employee,两个对象都有name属性,student有学号属性number,而 employee有薪水属性salary。employee先入栈,student后入栈,位于栈顶,那么对于表达式name,访问的就是student 的name属性,因为student对象位于栈顶;表达式salary,访问的就是employee的salary属性。正如你所见,访问值栈中的对象属性或方法,无须指明对象,也不用“#”,就好像值栈中的对象都是OGNL上下文中的根对象一样。这就是Struts2在OGNL基础上做出的改进。
Value Stack配合标签示例1
UserAction
userAction_result.jsp
上述JSP页面将能正确将它们的值取出。<s:property value=”ognl表达式”/>。在s:property标签中的OGNL表达式,最终会交给ValueStack来解释。username就 是一个OGNL表达式,意思是调用root对象的getUsername()方法。Struts2将自动搜索CompoundRoot中有哪些元素(从第 0个元素开始搜索),检测这些元素是否有getUsername()方法,如果第0个元素没有getUsername()方法,将继续搜索第1、2、 3……个元素是否有getUsername()方法。
CompoundRoot中只有一个对象,就是userAction对象,而这个对象中正好有getUsername()方法,所以,上述JSP代码将能够将值正确取出
改变默认栈顶元素
可以主动push新的对象到值栈中,也可以主动pop值栈的栈顶对象, 通过压栈和出栈操作,可以改变valueStack的结构
示例UserAction2.java
在上面这个UserAction的代码中,我们直接调用 ActionContext.getContext().getValueStack().push()方法,把一个User对象(这个对象拥有 getUsername()和setUsername()方法)直接压入到ValueStack中,这时候,在ValueStack的 CompoundRoot中将有两个元素:第0个元素是刚刚压入的user对象[李四],而第1个元素是userAction对象[张三]。
Struts的ONGL
值
栈中的
Action
实例
Struts2框架默认总是把Action实例放在栈顶。因为Action在值栈中,而值栈又是OGNL中的根,所以引用Action的属性可以省略“#”标记,这也是为什么我们在结果页面中可以直接访问Action的属性的原因。
Struts2
中的非
root
对象
如果访问Context其他对象,由于他们不是根对象,所以在访问时,需要添加#前缀;这些对象是直接保存在ActionContext中的,与valueStack平级;这些对象本质上都是Map
parameters
用于访问请求参数。如:#parameters[‘id’]或#parameters.id,相当于调用了 HttpServletRequest对象的getParameter()方法。
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对象。例如,#session[‘user’]=“zhangsan”,假定pageContext不可用,#attr[‘user’](或attr.user)将依次搜索request、session、application对象,最终在session中找到user属性。
采用OGNL表达式创建List/Map集合对象
如果需要一个集合元素的时候(例如List对象或者Map对象),可以使用OGNL中同集合相关的表达式。
使用如下代码直接生成一个List对象:
<s:setname="list" value="{'zhangming','xiaoi','liming'}" />
<s:iteratorvalue="#list" id="n">
<s:property value="n"/><br>
</s:iterator>
生成一个Map对象:
<s:setname="foobar" value="#{'foo1':'bar1','foo2':'bar2'}" />
<s:iteratorvalue="#foobar" >
<s:property value="key"/>=<s:property value="value"/><br>
</s:iterator>
Set标签用于将某个值放入指定范围。
scope:指定变量被放置的范围,该属性可以接受application、session、request、 page或action。如果没有设置该属性,则默认放置在valueStack中。
value:赋给变量的值.如果没有设置该属性,则将ValueStack栈顶的值赋给变量。
集合操作示例
UserAction3.java userAction_list.jsp
iterator标签将循环访问users这个List中的User对象,并把当 前循环的user对象压入到CompoundRoot中
向Action属性自动赋值实质
Strut2的Action类通过属性可以获得所有相关的值,如请求参数、Action配置参数、向其他Action传递属性值(通过chain结果)等等。
要获得这些参数值,我们要做的唯一一件事就是在Action类中声明与参数同名的属性
实现该特性就是依靠valueStack配合拦截器共同完成的
每个Action类的对象实例会拥有一个自己的ValueStack对象,这个值栈贯穿整个Action的生命周期
该过程本质上是这样的:
当Struts2接收到一个.action的请求后,会先建立Action类的对象实例,但并不会调用Action方法,而是先将Action类的相应属性放到ValueStack对象中,这时候所有的属性值都是默认的值,如String类型的属性值为null,int类型的属性值为0等
在处理完上述工作后,Struts 2就会调用拦截器链中的拦截器,当调用完所有的拦截器后,最后会调用Action类的相应方法。
在调用Action方法之前,会将ValueStack对象顶层节点中的属性值赋给Action类中相应的属性。也就是说,在Struts2调用拦截器的过程中,可以改变ValueStack对象中属性的值
在Struts2的的Action类可以获得与属性同名的参数值就是通过不同的拦截器来处理的,如获得请求参数的拦截器是params,获得Action的配置参数的拦截器是staticParams等。在这些拦截器内部读取相应的值,并更新ValueStack对象顶层节点的相应属性的值。而ValueStack对象就象一个传送带,将属性值从一个拦截器传到了另一个拦截器(当然,在这其间,属性值可能改变),最后会传到Action对象,并将ValueStack对象中的属性的值终值赋给Action类的相应属性。
这就是一种AOP的思想的体现,通过一些代理类来透明地完成一些公用功能