Struts ognl 和 valueStack详解
OGNL
OGNL简介
OGNL(ObjectGraphic Navigation Language)对象图导航语言,它是一个开源项目。
Struts2框架使用OGNL作为默认的表达式语言,大大加强了数据访问功能。
EL和OGNL的区别
EL表达式不能存放数据,不能调用方法。
OGNL表达式可以存放数据,可以调用方法。
IGNL两大方法
/**
* 根据OGNL表达式进行取值操作
*
* @param expression
* ognl表达式
* @param ctx
* ognl上下文
* @param rootObject
* ognl根对象
* @return
*/
public static Object getValue(String expression, OgnlContext ctx,
Object rootObject) {
try {
return Ognl.getValue(expression, ctx, rootObject);
} catch (OgnlException e) {
throw new RuntimeException(e);
}
}
/**
* 根据OGNL表达式进行赋值操作
*
* @param expression
* ognl表达式
* @param ctx
* ognl上下文
* @param rootObject
* ognl根对象
* @param value
* 值对象
*/
public static void setValue(String expression, OgnlContext ctx,
Object rootObject, Object value) {
try {
Ognl.setValue(expression, ctx, rootObject, value);
} catch (OgnlException e) {
throw new RuntimeException(e);
}
}
ognl的核心方法就这两个Ognl.getValue
和Ognl.setValue
已经能够完成对各种对象树的读取和设值工作了。需要特别强调进行区分的,是在针对不同内容进行取值或者设值时,OGNL表达式的不同。
针对根节点
针对根对象(Root Object)的操作,表达式是自根对象到被访问对象的某个链式操作的字符串表示。
针对非根节点
针对上下文环境(Context)的操作,表达式是自上下文环境(Context)到被访问对象的某个链式操作的字符串表示,但是必须在这个字符串的前面加上#
符号,以表示与访问根对象的区别。
OGNL三要素
我能先开一下案例,然后再去讲解这三要素
public static void main(String[] args) {
//员工对象
Employee e = new Employee();
e.setName("小李");
//管理者对象
Manager m = new Manager();
m.setName("张经理");
// 创建OGNL下文,而OGNL上下文实际上就是一个Map对象
OgnlContext ctx = new OgnlContext();
// 将员工和经理放到OGNL上下环境中去
ctx.put("employee", e);
ctx.put("manager", m);
// 设置OGNL上下文的根对象
ctx.setRoot(e);
/** ********************** 取值操作 *************************** */
// 表达式name将执行e.getName(),因为e对象是根对象(请注意根对象和非根对象表达式的区别)
//name没有加#,会直接去根对象中访问看看有没有一个叫做name的属性呀
String employeeName = (String) OnglExpression.getValue("name", ctx,e);
//根对象是e,就相对于输出了 e.getName 结果就 小李
System.out.println(employeeName);
// 表达式#manager.name将执行m.getName(),注意:如果访问的不是根对象那么必须在前面加上一个名称空间,例如:#manager.name
String managerName = (String) OnglExpression.getValue("#manager.name",ctx, e);
//输出结果是 张经理
System.out.println(managerName);
//当然根对象也可以使用#employee.name表达式进行访问
employeeName = (String) OnglExpression.getValue("#employee.name", ctx,
e);
//结果是 小李
System.out.println(employeeName);
/** ********************** 赋值操作 *************************** */
OnglExpression.setValue("name", ctx, e, "小明");
employeeName = (String) OnglExpression.getValue("name", ctx, e);
//这是将根对象(e)的name属性修改为了 小明 自然输出也是小明
System.out.println(employeeName);
//这是将存入上下文环境中的manager对象的name属性修改成了孙经理
OnglExpression.setValue("#manager.name", ctx, e, "孙经理");
managerName = (String) OnglExpression.getValue("#manager.name", ctx, e);
//输出:孙经理
System.out.println(managerName);
OnglExpression.setValue("#employee.name", ctx, e, "小芳");
employeeName = (String) OnglExpression.getValue("name", ctx, e);
//不解释和上面一样,输出小芳
System.out.println(employeeName);
}
控制台打印结果
小李
张经理
小李
小明
孙经理
小芳
从上面我们可以推理出OGNL操作的三要素
表单式(Expression)
表达式是整个OGNL的核心,所有的OGNL操作都是针对表达式的解析后进行的。表达式会规定此次OGNL操作到底要干什么。上面程序中getValue("name", ctx,e)
代表获取的是根对象的属性。因为没有通过#
号指定。getValue("#manager.name",ctx, e);
这个表达式就像是指名道姓我要拿谁谁谁的姓名。
根对象 (Root Object)
根对象可以理解为OGNL的操作对象。在表达式规定了“干什么”以后,你还需要指定到底“对谁干”。
在上面的测试代码中,e就是根对象。这就意味着,我们需要对e这个对象去取name这个属性的值
上下文环境 (Context)
有了表达式和根对象,我们实际上已经可以使用OGNL的基本功能。例如,根据表达式对根对象进行取值或者设值工作。不过实际上,在OGNL的内部,所有的操作都会在一个特定的环境中运行,这个环境就是OGNL的上下文环境(Context)。说得再明白一些,就是这个上下文环境(Context),将规定OGNL的操作“在哪里干”。
OGNL的上下文环境是一个Map结构,称之为OgnlContext。上面我们提到的根对象(Root Object),事实上也会被加入到上下文环境中去,并且这将作为一个特殊的变量进行处理,具体就表现为针对根对象(Root Object)的存取操作的表达式是不需要增#
符号进行区分的。
OgnlContext不仅提供了OGNL的运行环境。在这其中,我们还能设置一些自定义的parameter到Context中,以便我们在进行OGNL操作的时候能够方便的使用这些parameter。不过正如我们上面反复强调的,我们在访问这些parameter时,需要使用#作为前缀才能进行。应为他们不是根对象
Context 上下文对象
OGNL有一个上下文(Context)概念,说白了上下文就是一个Map结构,它实现了java.utils.Map接口,在Struts2中上下文实现为ActionContext。
前台发送请求访问action
当Struts2接受一个请求时,会迅速创建ActionContext,ValueStack,action
。然后把action存放进ValueStack,所以action的实例变量可以被OGNL访问。
访问上下文(Context)中的非根对象需要使用#符号标注命名空间,如#application、#session,另外OGNL会设定一个根对象(root对象),在Struts2中根对象就是ValueStack(值栈) 。如果要访问根对象(即ValueStack)中对象的属性,则可以省略#命名空间,直接访问该对象的属性即可。
值栈 ValueStack
valueStack是struts2的值栈空间,是struts2存储数据的空间
Strut2的Action类通过属性可以获得所有请求相关的值,要在Action类中声明与参数同名的属性或者以类名小写加属性名如:user.name,就可以获得这些参数值,在Struts2调用Action类的Action方法(默认是execute方法)之前,就会为相应的Action属性赋值。这项功能Struts2要依赖于ValueStack对象来实现,每个Action类的对象实例会拥有一个ValueStack对象,这个对象贯穿整个Action的生命周期,
当Struts2接收到一个请求后,会先建立Action类的对象实例,但并不会立即调用Action方法,而是先将Action类的相应属性放到ValueStack实现类ONGLValueStack对象root对象的顶层节点。只是所有的属性值都是默认的值,如String类型的属性值为null,int类型的属性值为0等。在处理完上述工作后,Struts 2就会调用拦截器链中的拦截器,这些拦截器会根据用户请求参数值去更新ValueStack对象顶层节点的相应属性的值,最后会传到Action对象,并将ValueStack对象中的属性值,赋给Action类的相应属性。当调用完所有的拦截器后,才会调用Action类的Action方法。ValueStack会在请求开始时被创建,请求结束时消亡。
值栈的数据结构
值栈分为两个部分:root和context
root
root是List类型并且是一个栈的结构实现的,就是使用他存放一组对象 ,在root变量中处于第一位的对象叫栈顶对象。通常我们在OGNL表达式里直接写上属性的名称即可访问root变量里对象的属性,搜索顺序是从栈顶对象开始寻找,如果栈顶对象不存在该属性,就会从第二个对象寻找,如果没有找到就从第三个对象寻找,依次往下访问,直到找到为止。
由于ValueStack(值栈)是Struts 2中OGNL的根对象,如果用户需要访问值栈中的对象,在JSP页面可以直接通过下面的EL表达式访问ValueStack(值栈)中对象的属性: ${foo} //获得值栈中某个对象的foo属性。如果访问其他Context中的对象,由于他们不是根对象,所以在访问时,需要添加#前缀
context
ValueStack中的Context域与ActionContext的Context域是相关联域。
ActionContext中存储了值栈的内存地址,可以理解为ValueStack的Context与ActionContext是同一个值。
值栈findValue方法获取值的套路
我们使用下面的案例来证明它时应该栈
/**
* 此例用于模拟struts2的值栈计算过程
*
* @param args
*/
public String test1() {
//valueStack是一个堆栈的容器, 特点:前进后出
ValueStack vs = ServletActionContext.getContext().getValueStack();
vs.push(new Employee("张雇员", 2000));// 1
vs.push(new Student("小明同学", "s001"));// 0
//栈的自定向下找字段,最近的获取并输出
System.out.println(vs.findValue("name"));
System.out.println(vs.findValue("salary"));
return "rs";
}
结果是
小明同学
2000
原因:栈是应该先进后出的结构,上面代码中Employee先入展,Student后入栈。所有Student在Employee的上面(也就是压着Employee)。
vs.findValue("name")
当我们获取name这个属性的时候,它会先去Student找找看看有没有name这个属性呀。找到了就返回。而Employee也有name属性,但是呢它在Student下面。所以不会被访问到。
vs.findValue("salary")
当我们获取salary这个属性的值,同样的带来,会先去Student里面找,由于Student没有定义salary这个属性,所以就去Employee里面找,Employee中定义了salary属性,值为2000.那就输出2000
valueStack的赋值套路
下面我们将通过一个代码案例来帮助你更好的理解值栈。
定义一个Action类
public class TestAction implements ModelDriven<Cal>{
private Cal cal;
private String num;
public String test() {
// TODO Auto-generated method stub
//cal使用的是ModelDriven赋值
System.out.println("cal:" + cal);
//num使用的是get/set赋值
System.out.println("num:" + num);
return "rs";
}
@Override
public Cal getModel() {
// TODO Auto-generated method stub
return cal;
}
}
Cal 代码
public class Cal{
private String num;
public String getNum() {
return num;
}
public void setNum(String num) {
this.num = num;
}
@Override
public String toString() {
return "Cal [num=" + num + "]";
}
}
注:这里就不展示为TestAction配置xml了
下面我们再前台访问TestAction的test方法并将参数num传过去。访问路径如下
/sy/testAction.action?num1=20
结果是 cal.num为20,全局变量num为null,并不是我们猜想的num也为20(公开了set/get方法)
原因当值栈通过前台参数给后台的TestAction的全局参数赋值时。值栈中cal这个变量在num这个变量的前,也就是更靠近栈顶。当赋值时会最先给cal赋值。就这样num的值赋进了cal的num属性中,前台参数已经被赋过了,所有后面的num就不会被赋值,自然就为null