- /**
- * @author Downpour
- */
- public class User {
- private Integer id;
- private String name;
- private Department department = new Department();
- // setter and getters
- }
- //=========================================================================
- /**
- * @author Downpour
- */
- 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>
- //=========================================================================
- /**
- * @author Downpour
- */
- public class OgnlAction extends ActionSupport {
- private static final Log logger = LogFactory.getLog(OgnlAction.class);
- private User user;
- private Department department;
- /* (non-Javadoc)
- * @see com.opensymphony.xwork2.ActionSupport#execute()
- */
- @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" />
ValueStack —— 对OGNL的加强
细心的读者可能会发现,在上面的例子中,我们使用了不同的表达式,针对Action中的不同的Java对象进行设值。再结合上一讲我们所例举的OGNL的代码操作示例,我们有强烈的理由怀疑,Struts2在内部有可能执行了这样的操作,才使得页面到Action的设值工作顺利完成:
- // "user.name" as OGNL expression, action as OGNL Root object
- 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的标签包装。
设值计算
Struts2中使用OGNL进行设值计算,就是指View层传递数据到Control层,并且能够设置到相应的Java对象中。这个过程从逻辑上说需要分成两步来完成:
1. 对于每个请求,都建立一个与相应Action对应的ActionContext作为OGNL的上下文环境和ValueStack,并且把Action压入ValueStack
2. 在请求进入Action代码前,通过某种通用的机制,搜集页面上传递过来的参数,并调用OGNL相关的代码,对Action进行设值。
上面的第一个步骤,在处理URL请求时完成,而第二个步骤,则涉及到另外一个XWork的核心知识:拦截器。所以有关Struts2使用OGNL进行设值计算的详细分析,将会在拦截器章节具体给出。