原文:http://www.javaeye.com/wiki/struts2/1356-how-to-use-ognl-in-struts2
OGNL 是为了能够使用对象的属性名来建立 UI 组件 (component) 和 控制器 (controllers) 之间的联系,简单来说就是:视图 与 控制器 之间数据的联系.他是XWork引入的一个非常有效的数据处理的工具。
从例子开始
我们先从一个例子开始,看看数据在Struts2中是如何运转的。
public class User {
private Integer id;
private String name;
private Department department = new Department();
// setter and getters
}
public class Department {
private Integer id;
private String name;
// setter and getters
}
<form method="post" action="/struts-example/ognl.action">
user name: <input type="text" name="user.name " value="downpour" />
department name: <input type="text" name="department.name " value="dev" />
<input type="submit" value="submit" />
</form>
public class OgnlAction extends ActionSupport {
private static final Log logger = LogFactory.getLog(OgnlAction.class);
private User user;
private Department department;
@Override
public String execute() throws Exception {
logger.info("user name:" + user.getName()); // -> downpour
logger.info("department name:" + department.getName()); // -> dev
return super.execute();
}
// setter and getters
}
//=========================================================================
user name: <s:property value="user.name" />
department name: <s:property value="department.name" />
//=========================================================================
我们可以看到在JSP中,form中的元素input等,都使用OGNL的表达式作为name的值。而在form提交时,这些值都会被设置到Action 中的Java对象中。而当Action转向到JSP时,Struts2的Tag又可以从Action的Java对象中,通过OGNL进行取值。
在这里,你看不到任何的OGNL的代码级别操作,因为这些都在Struts2内部进行了封装。而这些封装,都是建立在OGNL的基本概念,也就是根对象和上下文环境之上。下面就分别就这两个方面分别进行讲解。
ValueStack —— 对OGNL的加强
细心的读者可能会发现,在上面的例子中,我们使用了不同的表达式,针对Action中的不同的Java对象进行设值。再结合上一讲我们所例举的OGNL的 代码操作示例,我们有强烈的理由怀疑,Struts2在内部有可能执行了这样的操作,才使得页面到Action的设值工作顺利完成:
Ognl.setValue("user.name" , action, "downpour" );
Ognl.setValue("department.name" , action, "dev" );
如果这个怀疑是正确的,那么我们就能得出这样一个结论:Struts2的Action是OGNL操作的根对象。
这个结论是我们从现象上推出来的,至于它到底正确与否,我们之后可以通过源码分析来进行验证,在这里先卖一个关子,姑且认为它是正确的。不过这个结论对我们来说非常重要,因为这个结论对于Struts2的Tag,JSTL和Freemarker等表示层元素获取Action中变量的值打下了坚实的基础。
在Struts2(XWork)中,不仅把Action作为OGNL操作的根对象,作为对OGNL的扩展,它还引入了一个ValueStack 的概念。这个概念代表了什么呢?还是让我们看看Struts2的Reference怎么说:
很明显,ValueStack依照它的结构和作用,至少为我们提供两大特性:
1. ValueStack是一个堆栈结构,堆栈中的每个元素对于OGNL操作来说,都被看作是根对象。
2. 由于ValueStack是一个堆栈结构,所以其中的元素都是有序的,对于某个OGNL表达式来说,OGNL将自堆栈顶部开始查找,并返回第一个符合条件的对象元素。
这里我们有必要对第二点啰嗦几句,举个具体的例子来说(这个例子同样摘自Struts2的Reference):如果在ValueStack中有2个对象,分别是“动物”和“人”,这两个对象都具备一个属性,叫做name,而“动物”还有一个属性叫species,“人”还有个属性叫salary。其中,“动物”对象在ValueStack的栈顶,而“人”这个对象在栈底。那么看看下面的OGNL表达式将返回什么?
species // call to animal.getSpecies()
salary // call to person.getSalary()
name // call to animal.getName() because animal is on the top
对于name这个属性,返回的将是“动物”的name,因为“动物”在栈顶,会被先匹配到OGNL的表达式。但是有的时候,你可能需要访问“人”的name属性,怎么办呢?你可以通过下面的方法:
[0].name // call to animal.getName()
[1].name // call to person.getName()
Struts2中的OGNL上下文环境
有了ValueStack,我们再来仔细研究一下Struts2中OGNL的上下文环境。
也就是说,ActionContext是Struts2中OGNL的上下文环境。它维护着一个Map的结构,下面是这个结构的图示:
其中,ValueStack是这个上下文环境中的根对象,而除了这个根对象以外,Struts2还在这个上下文环境中放了许多额外的变量,而这些变量多数都是被XWork封装过的Servlet对象,例如request,session,servletContext(application)等,这些对象都被封装成Map对象,随着ActionContext作用于整个Action执行的生命周期中。
在这里,或许有些读者会提出问题来,为什么好好的Servlet对象要在这里被封装成Map对象呢?我想原因可能有以下两个:
1. 对Struts2的Action彻底屏蔽Servlet容器,从而无需再使用底层Servlet API进行编程。你所面对的,将永远是一个又一个的Java对象。
2. 便于各种View技术,例如JSP,Freemarker,Velocity等对ValueStack中上下文环境,尤其是Servlet对象中的数据进行读取。试想,如果在这里不将HttpServletRequest,HttpSession等Servlet对象转化成Map,那么我们将很难通过 OGNL表达式,对这些Servlet对象中的值进行读取。
Struts2中使用OGNL进行计算
取值计算
有了上面的这些知识,我们就能非常容易的理解在Struts2中如何使用OGNL进行取值计算。
提问: 在Struts2中,如何使用自身的Tag读取Action中的变量?
Struts2自身的Tag会根据value中的OGNL表达式,在ValueStack中寻找相应的对象。因为action在 ValueStack的顶部,所以默认情况下,Struts2的Tag中的OGNL表达式将查找action中的变量。请注意,value中的内容直接是 OGNL表达式,无需任何el的标签包装。
例如:<s:property value="user.name" />
提问: 在Struts2中,如何使用自身的Tag读取HttpServletRequest,HttpSession中的变量?
在上面的知识中,我们知道,Struts2中OGNL的上下文环境中,包含request,session,application等 servlet对象的Map封装。既然这些对象都在OGNL的上下文中,那么根据OGNL的基本知识,我们可以通过在表达式前面加上#符号来对这些变量的值进行访问。
例如:<s:property value="%{#application.myApplicationAttribute}" />
<s:property value="%{#session.mySessionAttribute}" />
<s:property value="%{#request.myRequestAttribute}" />
<s:property value="%{#parameters.myParameter}" />
设值计算
Struts2中使用OGNL进行设值计算,就是指View层传递数据到Control层,并且能够设置到相应的Java对象中。这个过程从逻辑上说需要分成两步来完成:
1. 对于每个请求,都建立一个与相应Action对应的ActionContext作为OGNL的上下文环境和ValueStack,并且把Action压入ValueStack
2. 在请求进入Action代码前,通过某种通用的机制,搜集页面上传递过来的参数,并调用OGNL相关的代码,对Action进行设值。
上面的第一个步骤,在处理URL请求时完成,而第二个步骤,则涉及到另外一个XWork的核心知识:拦截器。所以有关Struts2使用OGNL进行设值计算的详细分析,将会在拦截器章节具体给出。