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对象获取数据。
本教程由尚硅谷教育大数据研究院出品,如需转载请注明来源,欢迎大家关注尚硅谷公众号(atguigu)了解更多。