一、咄咄怪事
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中获取的。
本教程由尚硅谷教育大数据研究院出品,如需转载请注明来源。