OGNL表达式
什么是OGNL
OGNL表达式是Object-Graph Navigation Language的缩写,是一种功能强大的表达式语言,通过简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转换。在Struts1中,习惯使用的表达式语言是EL,在WebWork2和Struts2.x中使用OGNL来做页面数据绑定。利用表达式,可以直接利用对象曾的对象,更面向对象的操作使得项目不需要封装太多的FormBean。
OGNL取值范围
OGNL表达式是围绕OGNL上下文对象(即OgnlContext)对象进行的,OGNLContext对象分为Root跟Context两部分,Root可以存放任何对象,而Context中只能存放Map,而OGNL表达式会去这两部分取值
OGNL使用
导包
由于struts2的包中已经包含了OGNL的包,因此不需要导入额外的包
准备OGNL对象
// 准备Root User rootUser = new User("Scarlett", 33); // 准备Context Map<String, User> map = new HashMap<>(); map.put("user1", new User("Mike", 15)); map.put("user2", new User("Dick", 17)); OgnlContext oc = new OgnlContext(); oc.setRoot(rootUser); oc.setValues(map);
书写OGNL表达式
首先准备一个User类,如下
package com.jinxin.ognl; public class User { private String name; private Integer age; public User(String name, Integer age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }
取值
// 从Root中获取 String name = (String) Ognl.getValue("name", oc, oc.getRoot()); System.out.println(name); // 从Context中获取 Integer name2 = (Integer) Ognl.getValue("#user1.age", oc, oc.getRoot()); System.out.println(name2);
赋值
// 给Root中的对象的name属性复赋值 String newName = (String) Ognl.getValue("name='Taylor', name", oc, oc.getRoot()); System.out.println(newName); // 给Context中的user2的name属性赋值 Integer newAge = (Integer) Ognl.getValue("#user2.age = 100, #user2.age", oc, oc.getRoot()); System.out.println(newAge);
调用方法
// 调用Root中的对象的方法 String funcName = (String) Ognl.getValue("setName('hh'), getName()", oc, oc.getRoot()); System.out.println(funcName); // 调用Context对象中的user1对象的方法 Integer funcAge = (Integer) Ognl.getValue("#user1.setAge(1000), #user1.getAge()", oc, oc.getRoot()); System.out.println(funcAge);
调用静态方法
首先准备一个EClass类,如下
package com.jinxin.ognl; public class EClass { public static String echo(String param) { return param; } }
使用 完整类名@方法 的形式调用
String echo = (String) Ognl.getValue("@com.jinxin.ognl.EClass@echo('hello world')", oc, oc.getRoot()); System.out.println(echo);
创建LIst对象
// 使用size()方法验证这是一个List Integer size = (Integer) Ognl.getValue("{'Scarlett', 'Taylor', 'Eric'}.size()", oc, oc.getRoot()); // 可以使用 List[index] 的形式取值 Integer size = (Integer) Ognl.getValue("{'Scarlett', 'Taylor', 'Eric'}[0]", oc, oc.getRoot());
创建Map对象
Integer size = (Integer) Ognl.getValue("#{'name':'tom', 'age':'age'}.size()", oc, oc.getRoot()); // 使用 map[key] 的形式取值 String name = (String) Ognl.getValue("#{'name':'tom', 'age':'age'}['name']", oc, oc.getRoot());
OGNL表达式与Struts2结合
首先介绍一个对象,叫做值栈(ValueStack):
OGNL的核心就是OGNL上下文对象,即OgnlContext对象,而Struts2所提供的OgnlContext对象就是ValueStack对象,因此ValueStack也分为两部分,其中Root为栈结构,放置的是当前的Action对象,而Context对象存放的是ActionContext数据中心
Root栈结构的实现
上面提到值栈中的Root是一个栈结构,那么这个栈结构是如何实现的呢?要弄清楚这个问题首先得知道栈的特点,同队列的先进先出不同,栈结构是先入后出,就好像子弹压入弹夹一样,最先压进去的子弹在弹夹的最下方,反而是最后打出去的,那么知道这个特点后就可以着手实现这样一个栈结构了,这里选用List集合来实现,根据栈的特点,在压栈的时候把最新放入的数据始终存到集合的0号索引处 list.add(0, Obj) ,其他的值依次往后推,在弹栈的时候讲0号索引处的数据弹出 list.remove(0) ,后面的数据依次往前补
Struts2与ognl结合的体现
接收参数
前面有讲过struts2获取前端表单的参数的方法有三种,分别是 属性驱动,对象驱动,模型驱动,这些很“神奇”的方式都是通过ognl实现的
属性驱动如图
对象驱动如图
模型驱动如图
实现模型驱动的方法很简单,就是在参数赋值前将User对象压入栈顶即可。
获取值栈,并压栈
ValueStack v = ActionContext.getContext().getValueStack(); vs.push(user);
不过不能在Action中压,因为在请求到达Action之前会经过20个拦截器,赋值的操作在哪里就已经完成了,准确的说是在params拦截器中就已经赋值了,然后再到Action中讲User对象压入栈中还有什么意义呢?所以应该在params拦截器之前的拦截器中完成压栈操作
其中在一个叫做prepare的拦截器在params拦截器之前执行,在该拦截器中会执行一个prepare()方法,那么就可以在这个方法中将User压入栈中,之后在params拦截器中赋值就能找到User对象了。
那么要执行上述的操作必须先让当前的Action实现一个Preparable的接口,然后再实现prepare()方法,具体实现如下
public class DemoAction extends ActionSupport implements Preparable{ private User user = new User(); @override public void prepare() throws Exception{ // 获取ValueStack ValueStack v = ActionContext.getContext().getValueStack(); // 将user对象压入栈中 vs.push(user); } }
除了上面的方法以外,还有一种方法,在做模型驱动获取参数的时候也曾经实现过一个接口,叫做ModelDriven,在拦截器中有一个modelDriven,也在params拦截器之前执行,那么跟上面一样,同样可以将压栈的操作放到这个拦截器中执行。同样是在Avtion类中实现ModelDriven接口,然后实现 getModel() 方法,只是不再需要在getModel()方法中自己将user压进栈顶了,只需要讲user对象返回即可,在拦截器中接收到了这个对象就会执行压栈操作了,具体代码如下
public class DemoAction extends ActionSupport implements ModelDriven<User>{ private User user = new User(); public User getModel(){ return user; // 将user返回即可 } }
上述操作已经将user对象返回了,那么在拦截器中接收到后压入栈顶即可,如图
上图的 Object model = modelDriven.getModel(); 一行便是获取了user对象,然后再执行 stack.push(model); 将user压入栈顶
在配置文件中
有的时候在转发或者重定向到Action中的时候希望携带参数过去,这时也可以用到ognl表达式
<param name="actionName">Demo01Action</param> <param name="namespace">/demo</param> <param name="name">${name}</param> // 这里是ognl表达式,从栈顶寻找当前Action的name属性