0x00 参考

本笔记直接参考或引自如下链接文章:

http://c.biancheng.net/struts2/

https://www.w3cschool.cn/struts_2/

0x01 简介

值栈(ValueStack)就是 OGNL 表达式存取数据的地方。在一个值栈中,封装了一次请求所需要的所有数据。值栈是一组对象,按照提供的顺序存储以下这些对象:

序号 对象和说明
1 Temporary对象实际中存在各种在页面执行期间创建的temporary对象。例如,JSP标签循环集合的当前迭代值。
2 Model对象如果在struts应用程序中使用Model对象,则当前Model对象放在值堆栈上的action之前。
3 Action对象这是指正在执行的当前action对象。
4 命名对象这些对象包括#application,#session,#request,#attr和#parameters以及所引用的相应的servlet作用域。

在使用 Struts2 的项目中,Struts2 会为每个请求创建一个新的值栈,也就是说,值栈和请求是一一对应的关系,这种一一对应的关系使值栈能够线程安全地为每个请求提供公共的数据存取服务。

0x02 作用

值栈可以作为一个数据中转站在前台与后台之间传递数据,最常见的就是将 Struts2 的标签与 OGNL 表达式结合使用。值栈实际上是一个接口,在 Struts2 中利用 OGNL 时,实际上使用的就是实现了该接口的 OgnlValueStack 类,这个类是 OGNL 的基础。

0x03 生命周期

值栈贯穿整个 Action 的生命周期,每个 Action 类的对象实例都拥有一个 ValueStack 对象,在 ValueStack 对象中保存了当前 Action 对象和其他相关对象。

Struts2 框架把 ValueStack 对象保存在一个名为 struts.valueStack 的 request 属性中,也就是说,值栈与 Action 的生命周期一致。值栈的生命周期随着 request 的创建而创建,随着 request 的销毁而销毁。

0x04 值栈的获取方式

要获取值栈中存储的数据,首先应该获取值栈。

值栈的获取有两种方式。

在request中获取值栈

ValueStack 对象在 request 范围内的存储方式为 request.setAttribute("struts.valueStack",valuestack),可以通过如下方式从 request 中取出值栈的信息。

1
2
3
//获取 ValueStack 对象,通过 request 对象获取
ValueStack valueStack = (ValueStack)ServletActionContext.getRequest()
.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);

ServletActionContext.STRUTS_VALUESTACK_KEY 是 ServletActionContext 类中的常量,它的值为 struts.valueStack。

在ActionContext中获取值栈

在使用 Struts2 框架时,可以使用 OGNL 操作 Context 对象从 ValueStack 中存取数据,也就是说,可以从 Context 对象中获取 ValueStack 对象。实际上,Struts2 框架中的 Context 对象就是 ActionContext。

ActionContext 获取 ValueStack 对象的方式如下所示:

1
2
//通过 ActionContext 获取 valueStack 对象
ValueStack valueStack = ActionContext.getContext().getValueStack();

ActionContext 对象是在 StrutsPrepareAndExcuteFilter 的 doFilter() 方法中被创建的,在源码中用于创建 ActionContext 对象的 createActionContext() 方法内可以找到获取的 ValueStack 对象的信息。

方法中还有这样一段代码:

1
ctx=new ActionContext(stack.getContext());

从上述代码中可以看出,ValueStack 对象中的 Context 对象被作为参数传递给了 ActionContext 对象,这也就说明 ActionContext 对象中持有了 ValueStack 对象的引用,因此可以通过 ActionContext 对象获取 ValueStack 对象。

其实ValueStack就在ActionContext的request->struts.ValueStack中:

0x05 值栈的方法

一旦你有一个值栈对象,你可以使用以下方法来操纵该对象:

序号 值栈方法和说明
1 Object findValue(String expr)通过在默认搜索顺序中对值栈评估所给定的表达式来查找值。
2 CompoundRoot getRoot()获取将对象推入值栈的CompoundRoot。
3 Object peek()获取值栈顶部的对象而不改变值栈。
4 Object pop()获取值栈顶部的对象,并将其从值栈中删除。
5 void push(Object o)将对象放在值栈的顶部。
6 void set(String key,Object o)使用给定的key在值栈上设置一个对象,使其可通过findValue(key,…)检索。
7 void setDefaultType(Class defaultType)设置在获取值时要转换的默认类型。
8 void setValue(String expr,Object value)尝试使用由默认搜索顺序给定的表达式在值栈的bean上设置属性。
9 int size() 获取值栈中的对象数。

0x06 ValueStack、StackContext、ActionContext

具体作用的描述:

  • valueStack: 里面存放的是Action类中通过set方法设置的属性值(表单传过来的值等),由OGNL框架实现
  • stackContext: 也是用来存值的,stack上下文,它包含一些列对象,包括request/session/attr/application map等
  • actionContext: 是action的上下文,可以得到request,session,application等,我们在JSP页面中访问value stack的内容时,是不用加#,而如果是访问stack context的其他对象则要加上#

具体区别:

  • ActionContext 就是应用上下文,可以通过他来访问session对象等,可以保存的数据,以map形式存储,在这个应用中都可以访问该数据。通常用于还回一个session对象。
  • ValueStack 是OGNL表达式语言中的值栈, 用来封装继承了action接口的类的属性值,以栈结构存储,在同一个请求范围类有效,在页面中直接通过表达式${属性名}就可以取出
  • 值栈也称为栈结构,ActionContext也称为映射结构,表示值栈的context
  • ValueStack和ActionContext本质上可以互相获得。ValueStack.getContext()方法得到的Map其实就是ActionContext的Map

ValueStack的内容

经过调试发现:

值得注意的以下几点:

  • context实际上就是ActionContext的context内容;
  • securityMemberAccess是后续的Struts2一直发展的安全措施;

ActionContext的内容

ActionContext主要维护一个context对象,我们下面称为ContextMap。

一般情况下,root 对象在存储 Action 的相关信息时会把相关的映射压入ContextMap中,这些相关的映射具体如下:

key key的声明处 value的类型 value.toString()
com.opensymphony.xwork2.dispatcher.HttpServletRequest StrutsStatics.HTTP_REQUEST org.apache.struts2.dispatcher.StrutsRequestWrapper org.apache.struts2.dispatcher.StrutsRequestWrapper@10984e0
application org.apache.struts2.dispatcher.ApplicationMap
com.opensymphony.xwork2.ActionContext.locale ActionContext.LOCALE java.util.Locale zh_CN
com.opensymphony.xwork2.dispatcher.HttpServletResponse StrutsStatics.HTTP_RESPONSE org.apache.catalina.connector.ResponseFacade org.apache.catalina.connector.ResponseFacade@14ecfe8
xwork.NullHandler.createNullObjects Boolean false
com.opensymphony.xwork2.ActionContext.name ActionContext.ACTION_NAME String index
com.opensymphony.xwork2.ActionContext.conversionErrors ActionContext.CONVERSION_ERRORS java.util.HashMap {}
com.opensymphony.xwork2.ActionContext.application ActionContext.APPLICATION org.apache.struts2.dispatcher.ApplicationMap
attr org.apache.struts2.util.AttributeMap org.apache.struts2.util.AttributeMap@133a2a8
com.opensymphony.xwork2.ActionContext.container ActionContext.CONTAINER com.opensymphony.xwork2.inject.ContainerImpl com.opensymphony.xwork2.inject.ContainerImpl@fc02c8
com.opensymphony.xwork2.dispatcher.ServletContext StrutsStatics.SERVLET_CONTEXT org.apache.catalina.core.ApplicationContextFacade org.apache.catalina.core.ApplicationContextFacade@11ad78c
com.opensymphony.xwork2.ActionContext.session ActionContext.SESSION org.apache.struts2.dispatcher.SessionMap {}
com.opensymphony.xwork2.ActionContext. actionInvocation ActionContext.ACTION_INVOCATION com.opensymphony.xwork2.DefaultActionInvocation com.opensymphony.xwork2.DefaultActionInvocation@13d4497
xwork.MethodAccessor.denyMethodExecution 笔者很懒,没有找 Boolean false
report.conversion.errors 笔者很懒,没有找 Boolean false
session org.apache.struts2.dispatcher.SessionMap {}
com.opensymphony.xwork2.util.ValueStack.ValueStack ValueStack.VALUE_STACK com.opensymphony.xwork2.ognl.OgnlValueStack com.opensymphony.xwork2.ognl.OgnlValueStack@16237fd
request org.apache.struts2.dispatcher.RequestMap
action 笔者很懒,没有找 com.example.MyAction
struts.actionMapping 笔者很懒,没有找 org.apache.struts2.dispatcher.mapper.ActionMapping org.apache.struts2.dispatcher.mapper.ActionMapping@892cc5
parameters java.util.HashMap {}
com.opensymphony.xwork2.ActionContext.parameters ActionContext.PARAMETERS java.util.TreeMap

0x06 值栈内部结构详解

ValueStack 对象的内部有两个逻辑部分。

  • ObjectStack(对象栈):是 CompoundRoot 类型,用 ArrayList 定义,Struts2 把动作和相关对象压入 ObjectStack 中。
  • ContextMap(Map 栈):是 OgnlContext 类型,是一个 Map 集合,Struts2 把各种各样的映射关系(一些 Map 类型的对象)压入 ContextMap 中。

Demo

新建一个名称为 ValueStackAction 的类,并在类中编写一个获取 ValueStack 对象的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.mi1k7ea.ognl;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.util.ValueStack;

public class ValueStackAction extends ActionSupport {
public String execute() throws Exception {
// 通过ActionContext获取valueStack对象
ValueStack valueStack = ActionContext.getContext().getValueStack();
System.out.println(valueStack);
return SUCCESS;
}
}

将新建的 Action 信息添加到 struts.xml 中:

1
2
3
<action name="valueStack" class="com.mi1k7ea.ognl.ValueStackAction">
<result name="success">index.jsp</result>
</action>

最后,开启远程调试,在 ValueStackAction 类中的第 11 行处设置断点,访问http://localhost:8080/st2test/valueStack.action,从 Variables 窗口中可以看到 valueStack 的结构信息:

这里只需关注 context 对象和 root 对象。从图中可以看到,context 对象的类型为 OgnlContext,root 对象的类型为 CompoundRoot。如果要查看这两个类的源码,则可以看到如下语句:

1
2
OgnlContext extends Object implements Map
CompoundRoot extends ArrayList

从上述两个类的源码中可以看出,context 对象实际上就是一个 Map,root 对象实际上就是一个 ArrayList。也就说明了 ValueStack 的两个逻辑部分 ObjectStack 对应 ArrayList(root),ContextMap 对应 Map(context)。

一般情况下,root 对象在存储 Action 的相关信息时会把相关的映射压入 ContextMap 中,这些相关的映射具体如下。

  • parameters:该 Map 中包含当前请求的请求参数。
  • request:该 Map 中包含当前 request 对象中的所有属性。
  • session:该 Map 中包含当前 session 对象中的所有属性。
  • application:该 Map 中包含当前 application 对象中的所有属性。
  • attr:该 Map 按如下顺序检索某个属性:request,session,application。

0x07 获取值栈数据的方式

在 Struts2 中,值栈的主要作用就是解决从 Action 到页面的数据交换问题。在采用属性驱动和模型驱动交换数据时,Struts2 会将对象自动存储到 ValueStack 中,其存储说明如下:

  • 属性驱动:每次请求访问 Action 的对象时,Action 中的属性对象会被自动压入 ValueStack 中。
  • 模型驱动:Action 如果实现了 ModelDriven 接口,那么 ModelDrivenInterceptor 拦截器会生效,会将 model 对象压入到 ValueStack 中。

属性对象或 model 对象存储到 ValueStack 中后,就可以直接从 ValueStack 中获取页面所需的数据。

Demo参考:http://c.biancheng.net/view/4145.html

0x08 通过EL访问值栈的数据

具体参考:http://c.biancheng.net/view/4146.html