什么是OGNL?
OGNL(Object-Graph Navigation Language的简称),对象图导航语言,它是一门表达式语言,使用这种表达式语言,你可以通过某种表达式语法,存取Java对象树中的任意属性、调用Java对象树的方法、同时能够自动实现必要的类型转化。如果我们把表达式看做是一个带有语义的字符串,那么OGNL无疑成为了这个语义字符串与Java对象之间沟通的桥梁。
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
A) 针对根对象(Root Object)的操作,表达式是自根对象到被访问对象的某个链式操作的字符串表示。
B) 针对上下文环境(Context)的操作,表达式是自上下文环境(Context)到被访问对象的某个链式操作的字符串表示,但是必须在这个字符串的前面加上#符号,以表示与访问根对象的区别。
OGNL三要素:表达式,上下文,根对象
- 表达式(Expression)
表达式是整个OGNL的核心,所有的OGNL操作都是针对表达式的解析后进行的。表达式会规定此次OGNL操作到底要干什么。我们可以看到,在上面的测试中,name、department.name等都是表达式,表示取name或者department中的name的值。OGNL支持很多类型的表达式,之后我们会看到更多。 -
根对象(Root Object)
根对象可以理解为OGNL的操作对象。在表达式规定了“干什么”以后,你还需要指定到底“对谁干”。操作根对象的表达式不需要加’#’
- 上下文环境(Context)
有了表达式和根对象,我们实际上已经可以使用OGNL的基本功能。例如,根据表达式对根对象进行取值或者设值工作。不过实际上,在OGNL的内部,所有的操作都会在一个特定的环境中运行,这个环境就是OGNL的上下文环境(Context)。说得再明白一些,就是这个上下文环境(Context),将规定OGNL的操作“在哪里干”。
OGNL的上下文环境是一个Map结构,称之为OgnlContext。上面我们提到的根对象(Root Object),事实上也会被加入到上下文环境中去,并且这将作为一个特殊的变量进行处理,具体就表现为针对根对象(Root Object)的存取操作的表达式是不需要增加#符号进行区分的。
OgnlContext不仅提供了OGNL的运行环境。在这其中,我们还能设置一些自定义的parameter到Context中,以便我们在进行OGNL操作的时候能够方便的使用这些parameter。不过我们在访问这些parameter时,需要使用#作为前缀才能进行。
OGNL的基本功能测试
以上我们了解了OGNL的基本概念,下面我们通过代码来详细了解一下OGNL的功能和使用。
需要的两个JavaBean类
Users.java
/**
*
*/
package entity;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author Administrator
*
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Users implements Serializable{
/**
*
*/
private static final long serialVersionUID = 6879304390797686316L;
private String username;
private String password;
}
Student.java
/**
*
*/
package entity;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author Administrator
*
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student implements Serializable{
/**
*
*/
private static final long serialVersionUID = -4248135347869749538L;
private String name;
private String sex;
private int age;
}
OGNL测试代码
/**
*
*/
package ognl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import entity.Student;
import entity.Users;
/**
* @author Administrator
*
*/
public class OgnlTest {
/**
* 测试ognl的使用和方法的调用
*/
@Test
public void ognl1() {
Student student = new Student("学生张三", "男", 17);
Users user = new Users("用户李四", "123456");
Users users = new Users("根用户", "123456");
// 构建一个OgnlContext对象
OgnlContext context = new OgnlContext();
// 设置用户为根对象
context.setRoot(users);
context.put("user", user);
context.put("student", student);
// 构建Ognl表达式的树状表示,用来获取
try {
// 解析树状表达式,返回结果
Object rootName = Ognl.getValue("username", context, context.getRoot());
Object userName = Ognl.getValue("#user.username", context, context.getRoot());
System.out.println(rootName + ":" + userName);
// 从根对象中直接获取用户名称长度,非静态方法
Object expression = Ognl.parseExpression("#user.username.length()");
Object length = Ognl.getValue(expression, context, context.getRoot());
System.out.println("非静态方法获取密码用户名长度"+length);
// 在Ognl表达式中使用静态方法
expression = Ognl.parseExpression("@java.lang.Integer@valueOf(#user.password)");
length = Ognl.getValue("@java.lang.Integer@valueOf(#user.password)", context, context.getRoot());
System.out.println("静态方法获取密码"+length);
} catch (OgnlException e) {
e.printStackTrace();
}
}
/**
* 测试Ognl表达式中如何处理数组、集合和Map对象
* @throws OgnlException
*/
@Test
public void ognl2() throws OgnlException {
OgnlContext context = new OgnlContext();
// 通过Ognl表达式构建一个LinkedList对象,这注意:一定是包名+类名的形式
Object list = Ognl.parseExpression("new java.util.LinkedList()");
Object obj = Ognl.getValue(list, context, context.getRoot());
System.out.println(obj);
System.out.println("----------------------------");
// 在Ognl中提供了一种类似数组索引的方式访问集合指定位置的元素
// 下述例子直接构建了一个包含aa, bb, cc, dd四个元素的集合,然后访问集合中的第三个元素
Object object15 = Ognl.getValue("{'aa', 'bb', 'cc', 'dd'}[2]", context, context.getRoot());
System.out.println(object15);
System.out.println("----------------------------");
// 处理数组类型
String[] strs = new String[] { "aa", "bb", "cc" };
context.put("strs", strs);
System.out.println(Ognl.getValue("#strs[1]", context, context.getRoot()));
System.out.println("----------------------------");
// 处理集合类型
List<String> words = new ArrayList<String>();
words.add("hello");
words.add("world");
words.add("hello world");
context.put("words", words);
System.out.println(Ognl.getValue("#words[0]", context, context.getRoot()));
System.out.println("----------------------------");
// 处理Map类型,注意的是为了与集合区分开,在大括号前面加"#"
System.out.println(
Ognl.getValue("#{'key1': 'value1', 'key2': 'value2', 'key3': 'value3', 'key4': 'value4'}['key3']",
context, context.getRoot()));
}
/**
* 测试Ognl表达式的过滤和投影
* @throws OgnlException
*/
@Test
public void ognl3() throws OgnlException {
Student s1 = new Student("Tom", "男", 18);
Student s2 = new Student("Jack", "女", 17);
Student s3 = new Student("Tomas", "男", 40);
Student s4 = new Student("Lucy", "女", 16);
List<Student> stus = new ArrayList<Student>();
Collections.addAll(stus, s1, s2, s3, s4);
// 新建OgnlContext对象
OgnlContext context = new OgnlContext();
context.put("stus", stus);
// 过滤(filtering),collection.{? expression}
// 利用过滤获取年龄在17以上的所有学生集合
// 输出结果:[Student(name=Tom, sex=男, age=18), Student(name=Tomas, sex=男, age=40)]
System.out.println(Ognl.getValue("#stus.{? #this.age > 17}", context, context.getRoot()));
// 过滤(filtering),collection.{^ expression}
// 利用过滤获取年龄在17以上的所有学生集合中第一个元素
// 输出结果:[Student(name=Tom, sex=男, age=18)]
System.out.println(Ognl.getValue("#stus.{^ #this.age > 17}", context, context.getRoot()));
// 过滤(filtering),collection.{$ expression}
// 利用过滤获取年龄在17以上的所有学生集合的最后一个元素
// 输出结果:[Student(name=Tomas, sex=男, age=40)]
System.out.println(Ognl.getValue("#stus.{$ #this.age > 17}", context, context.getRoot()));
// 投影(projection), collection. {expression}
// 获取集合中的所有学生的姓名
// 输出结果:[Tom, Jack, Tomas, Lucy]
System.out.println(Ognl.getValue("#stus.{name}", context, context.getRoot()));
}
}
通过以上代码我们可以看到OGNL的功能非常强大,十分简洁的表达式就可以映射相应的java对象
需要注意的是OGNL的过滤和投影
- 过滤指的是将原集合中不符合条件的对象过滤掉,然后将满足条件的对象,构建一个新的集合对象返回,Ognl过滤表达式的写法是:collection.{?|^|$ expression};
-
投影指的是将原集合中所有对象的某个属性抽取出来,单独构成一个新的集合对象返回,基础语法为 :collection.{expression};
Struts2和OGNL
Struts 2中的OGNL Context实现者为ActionContext,它结构示意图如下:
当Struts2接受一个请求时,会迅速创建ActionContext,ValueStack,action 。然后把action存放进ValueStack,所以action的实例变量可以被OGNL访问。访问上下文(Context)中的对象需要使用#符号标注命名空间,如#application、#session,另外OGNL会设定一个根对象(root对象),在Struts2中根对象就是ValueStack(值栈) 。如果要访问根对象(即ValueStack)中对象的属性,则可以省略#命名空间,直接访问该对象的属性即可。
由于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对象:用于按page->request->session->application顺序访问其属性。
ActionContext和OGNL关系
Strut2的Action类通过属性可以获得所有相关的值,如请求参数属性值等。要获得这些参数值,我们要做的唯一一件事就是在Action类中声明与参数同名的属性。在Struts2调用Action类的Action方法(默认是execute方法)之前,就会为相应的Action属性赋值。要完成这个功能,有很大程度上,Struts2要依赖于ValueStack对象。这个对象贯穿整个Action的生命周期,每个Action类的对象实例会拥有一个ValueStack对象。
当Struts2接收到一个.action的请求后,会先建立Action类的对象实例,但并不会调用Action方法,而是先将Action类的相应属性放到ValueStack对象的顶层节点(ValueStack对象相当于一个栈)。只是所有的属性值都是默认的值,如String类型的属性值为null,int类型的属性值为0等。在处理完上述工作后,Struts 2就会调用拦截器链中的拦截器,这些拦截器会根据用户请求参数值去更新ValueStack对象顶层节点的相应属性的值,最后会传到Action对象,并将ValueStack对象中的属性值,赋给Action类的相应属性。当调用完所有的拦截器后,才会调用Action类的Action方法。ValueStack会在请求开始时被创建,请求结束时消亡。
也就是说valuestack 是与Action类一一对应的
1. Struts2中将ActionContext作为OGNL的上下文环境(ActionContext内部含有一个Map对象)
2. Struts2中的OGNL表达式语言的根对象是一个ValueStack,ValueStack中的每一个对象都被视为根对象。
Struts2框架将实例化的Action对象放入ValueStack中,如果是Action链,则多个Action都存在于ValueStack中。而ValueStack中除了Action外,Struts2框架还将parameters,request,response,session,application,attr等对象放到ActionContext中,访问这些对象需要加前缀#。
Struts2中EL的查找域为:page(PageContext)–>request–>actionContext(contextMap)–>ValueStack–>session–>application
OGNL的查找域为:page(PageContext)–>ValueStack–>actionContext(contextMap)–>request–>session–>application
如何获得ActionContext?
1.在自定义的拦截器中:使用ActionInvocation.getInvocationContext()或者使用ActionContext.getContext()。
2.在Action类中:让拦截器注入或者使用ActionContext.getContext()。
3.在非Action类中:让Action类传递参数、使用注入机制注入或者使用ActionContext.getContext()。
注意:只有运行在request线程中的代码才能调用ActionContext.getContext(),否则返回的是null。
获取值栈的方法
方法1:
ValueStack valueStack1 = (ValueStack) ServletActionContext.getRequest().getAttribute("struts.valueStack");
方法2:
//获取值栈的第二种方式
ValueStack valueStack2 = ActionContext.getContext().getValueStack();
分析源码:
ActionContext源码:
public static final String VALUE_STACK = ValueStack.VALUE_STACK;
public ActionContext(Map<String, Object> context) {
this.context = context;
}
public void setValueStack(ValueStack stack) {
put(VALUE_STACK, stack);
}
public ValueStack getValueStack() {
return (ValueStack) get(VALUE_STACK);
}
public void setApplication(Map<String, Object> application) {
put(APPLICATION, application);
}
public Map<String, Object> getApplication() {
return (Map<String, Object>) get(APPLICATION);
}
public void put(String key, Object value) {
context.put(key, value);
}
...
其他都是类似的
org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter中的doFilter方法创建ActionContext并且创造了ValueStack
public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {
ActionContext ctx;
Integer counter = 1;
Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
if (oldCounter != null) {
counter = oldCounter + 1;
}
ActionContext oldContext = ActionContext.getContext();
旧的ActionContext存在时,直接获取赋值
if (oldContext != null) {
// detected existing context, so we are probably in a forward
ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap()));
} else {
否则通过ValueStack的getContext方法获取Map来构造新的ActionContext
ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext));
ctx = new ActionContext(stack.getContext());
}
request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
ActionContext.setContext(ctx);
return ctx;
}