Struts2值栈

一、咄咄怪事

1.奇怪的EL表达式

给一个目标Action类发送一个Struts2请求,假设目标Action类中包含如下方法:

public String getMessage() {

   return "I am very happy in atguigu!";

}

则在结果JSP页面上可以使用如下EL表达式获取上述方法的返回值:

${requestScope.message }

 

但毫无疑问我们根本没有将任何数据以message为属性名保存到请求域中,那么这个奇怪的EL表达式是如何读取到数据的呢?

 

2.偷梁换柱的getAttribute()方法

我们逐步来探究一下上面提出的问题。

 

①第一步

根据EL表达式语法,${requestScope.message }会被翻译成如下代码:

request.getAttribute("message");

 

②第二步

在页面上直接输出request对象得到如下结果:

org.apache.struts2.dispatcher.StrutsRequestWrapper@756ea3b2

 

而由Tomcat创建的request对象本来应该是:

org.apache.catalina.connector.RequestFacade@39e2cdaa

 

说明request对象已经不是我们在纯Servlet容器环境下使用的request对象了,而是一个经过Struts2包装过的request对象。

 

③第三步

StrutsRequestWrapper通过继承javax.servlet.http.HttpServletRequestWrapper类对原始的request对象进行了包装,仅修改了getAttribute()一个方法的行为。代码如下:

/**

 * 首先从原始的请求域中获取属性值,如果找不到就从值栈中读取

 *

 * @param key  请求域数据的键

 */

public Object getAttribute(String key) {

    if (key == null) {

        throw new NullPointerException("You must specify a key value");

    }

 

    //如果禁用了从值栈读取数据的功能或key是以javax.servlet开始的则从原始的请求域中读取数据并直接返回

    if (disableRequestAttributeValueStackLookup || key.startsWith("javax.servlet")) {

        return super.getAttribute(key);

    }

 

    //获取ActionContext对象

    ActionContext ctx = ActionContext.getContext();

   

    //尝试从原始的请求域中读取数据

    Object attribute = super.getAttribute(key);

 

    //如果ActionContext对象不为空且从原始的请求域中没有获取到指定数据

    if (ctx != null && attribute == null) {

       

        boolean alreadyIn = isTrue((Boolean) ctx.get(REQUEST_WRAPPER_GET_ATTRIBUTE));

 

        if (!alreadyIn && !key.contains("#")) {

            try {

                ctx.put(REQUEST_WRAPPER_GET_ATTRIBUTE, Boolean.TRUE);

               

                //通过ActionContext对象获取值栈对象

                ValueStack stack = ctx.getValueStack();

               

                if (stack != null) {

                    //如果值栈对象不为空,则尝试根据key“查找数据

                    attribute = stack.findValue(key);

                }

            } finally {

                ctx.put(REQUEST_WRAPPER_GET_ATTRIBUTE, Boolean.FALSE);

            }

        }

    }

    return attribute;

}

结论就是在Struts2环境下,request对象的getAttribute()方法会首先从请求域中获取数据,如果获取不到,则通过ValueStack对象获取数据。

 

二、ValueStack

1.概述

ValueStack是一个接口

com.opensymphony.xwork2.util.ValueStack

 

它的实现类是

com.opensymphony.xwork2.ognl.OgnlValueStack

 

①作用

Struts2为每一个请求都分配了一个ValueStack对象,目的是为每一个请求都提供一个临时的数据存储空间。

 

②两个数据容器

分析OgnlValueStack源码,其中包含两个重要的数据容器

    CompoundRoot root;//通常称为“对象栈”

    transient Map<String, Object> context;//通常称为“Map栈”

 

③对象栈

[1]CompoundRoot类声明

//继承自ArrayList

public class CompoundRoot extends ArrayList {

[2]CompoundRoot是在List基础之上实现的“栈”——后进先出。

    //返回栈顶对象

    public Object peek() {

        return get(0);

    }

 

    //删除栈顶对象并返回

    public Object pop() {

        return remove(0);

    }

 

    //将对象压入栈顶

    public void push(Object o) {

        add(0, o);

    }

 

[3]如何方便的查看值栈中的数据?

            (1)在JSP页面上导入Struts2标签库

<%@ taglib uri="/struts-tags" prefix="s" %>

 

            (2)使用<s:debug></s:debug>标签

            (3)点击[Debug]超链接即可展开值栈数据列表

[4]对象栈中的数据

            (1)默认情况下对象栈中的数据

可以看到,默认情况下Struts2会将当前Action对象压入值栈的栈顶,前面提及的message属性值就是从栈顶Action对象中获取的。

            如何理解“当前Action”?

            ●当前请求的目标Action

            ●转发到当前页面的“来源”Action

            (2)手动压入对象后

④Map栈

     [1]似曾相识的Map栈对象

     大家是否还记得,在获取Web资源时用到的ActionContext类中,有一个成员变量指向了一个Map,它的声明如下

private Map<String, Object> context;

     而我们OgnlValueStack类中也有一个context成员变量指向了一个Map对象,声明如下

transient Map<String, Object> context;

     他们指向的是同一个对象吗?我们可以比较一下

//1.从值栈对象中获取context对象

Map<String, Object> contextFromVS = valueStack.getContext();

      

//2.ActionContext对象中获取context对象

Map<String, Object> contextFromAC = ActionContext.getContext().getContextMap();

      

//比较这两种方式获取的context对象,返回true

System.out.println(contextFromVS == contextFromAC);

     通过程序验证我们发现,contextFromVS和contextFromAC指向同一块内存区域,证明他们指向的是同一个Map对象。它们之间的关系可以用下图表示:

     [2]context对象中的内容

     context对象中的内容可以分为如下几个部分[由于context对象是Map类型下面使用key/vlaue形式展示]:

(1)原生的Web资源对象

Key

Value

com.opensymphony.xwork2.dispatcher.HttpServletRequest

Struts2包装过的Request对象

com.opensymphony.xwork2.dispatcher.HttpServletResponse

原生的response对象

com.opensymphony.xwork2.dispatcher.ServletContext

原生的ServletContext对象

补充说明:看到这里大家可能会想,之前获取Web资源时ActionContext给我们提供的是封装相关数据的Map对象呀?怎么还有原生的Web对象呢?原生的Web对象不是从ServletActionContext中获取的吗?其实大家看看ServletActionContext的源码就能够发现,它获取原生Web资源对象本质上也是从ActionContext中获取的。

public static HttpServletRequest getRequest() {

return (HttpServletRequest) ActionContext.getContext().get(HTTP_REQUEST);

}

包括Struts2为实现了XxxAware、ServletXxxAware接口的Action对象注入Web资源对象本质上也都是从ActionContext中获取的。

当然ActionContext获取Web资源对象时也都是从context对象这个Map中获取的。

 

 


本教程由尚硅谷教育大数据研究院出品,如需转载请注明来源。
 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值