什么是值栈 ValueStack
回顾web阶段数据交互:
客户端提交数据到服务器端:request接受数据,BeanUtils实体封装。
服务器端数据在页面上显示:在服务器端将数据存储到request域中,页面中通过el+jstl完成数据展示。
struts2阶段数据交互:
客户端提交数据到服务器端:属性驱动和模型驱动。
服务器端数据 在页面上显示:在服务器端将数据存储到值栈中,在页面中通过ognl+struts2标签数据展示。
由此可见:
request域==值栈
el表达式==ognl表达式
jstl标签==struts2标签
值栈是什么?
值栈是服务器与客户端进行数据交互的数据中心,用于存储数据。
在服务器将数据存储到值栈中 在页面中从值栈取出数据进行展示。
值栈ValueStack是Struts2提供接口规范,于此同时也提供了该接口的实现OgnlValueStack。
值栈的生命周期?
值栈的内部结构
获得值栈对象:
ActionContext context = ActionContext.getContext();
VAlueStack valueStack = context.getValueStack();
值栈的内部结构:
Root栈:ArrayList
context栈:map
public class ValueStackAction extends ActionSupport implements ModelDriven<User>{
//获得值栈对象 查看其内部结构
public String show(){
//获得Action的上下文对象
ActionContext context = ActionContext.getContext();
//获得值栈对象
ValueStack valueStack = context.getValueStack();
System.out.println(valueStack);
return NONE;
}
}
值栈对象何时创建?ValueStack 和 ActionContext是什么关系(了解)
源码分析:
--->StrutsPrepareAndExecuteFilter
--->public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
--->prepare.createActionContext(request, response);
--->ValueStack stack = createValueStack();
ActionContext内部维护值栈的引用
如何获得值栈对象ValueStack
第一种方式:(掌握)
ActionContext context = ActionContext.getContext();
VAlueStack valueStack = context.getValueStack();
第二种方式:(了解)
通过request域对象 request.getAttribute("struts.valueStack");
源码剖析:
--->StrutsPrepareAndExecuteFilter
--->public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
--->execute.executeAction(request, response, mapping);
--->dispatcher.serviceAction(request, response, mapping);
--->request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
public static final String STRUTS_VALUESTACK_KEY = "struts.valueStack"
public class ValueStackAction extends ActionSupport implements ModelDriven<User>{
//测试值栈的获取方式
public String show(){
//获得Action的上下文对象
ActionContext context = ActionContext.getContext();
//获得值栈对象
ValueStack valueStack1 = context.getValueStack();
//通过request获取
ValueStack valueStack2 = (ValueStack) ServletActionContext.getRequest().getAttribute("struts.valueStack");
System.out.println(valueStack1);
System.out.println(valueStack2);
return NONE;
}
}
如何手动向值栈保存数据 (了解)
1、向Root栈存储数据
public class ValueStackAction extends ActionSupport implements ModelDriven<User>{
//测试向Root栈存储数据(了解)
public String show(){
//获得值栈对象
ValueStack valueStack = ActionContext.getContext().getValueStack();
//获得内部的Root栈
CompoundRoot root = valueStack.getRoot();
//存数据
root.add("itcast"); //存储到尾部
root.add(0, "itheima"); //存储到头部(栈顶)
//CompoundRoot的存取数据的API
root.push("xxx"); //压栈 add(0, o);
root.peek(); //获得栈顶的值 return get(0);
root.pop(); //弹栈 return remove(0);
return NONE;
}
}
2、向context栈存储数据
public class ValueStackAction extends ActionSupport implements ModelDriven<User>{
//测试向context栈存储数据(了解)
public String show(){
//获得值栈对象
ValueStack valueStack = ActionContext.getContext().getValueStack();
//获得context栈
Map<String, Object> map = valueStack.getContext();
map.put("xxx", "XXX");
map.get("xxx");
return NONE;
}
}
3、valueStack对象维护着向值栈存取数据的API
public class ValueStackAction extends ActionSupport implements ModelDriven<User>{
//测试valueStack对象向值栈存储值的APi
public String show(){
//获得值栈对象
ValueStack valueStack = ActionContext.getContext().getValueStack();
//压栈
valueStack.push("xxx"); //root.push(o);
//取值
valueStack.peek();//return root.peek();
valueStack.pop();//return root.pop()
//向root栈设置 将key-value键值对封装到一个Map中 在将Map压栈
/*
* 源码分析:
* public void set(String key, Object o) {
Map setMap = retrieveSetMap(); 该map是哪个??????
setMap.put(key, o); 将数据存储到map中
}
private Map retrieveSetMap() {
Map setMap; 定义一个map
Object topObj = peek(); 取出栈顶的值
if (shouldUseOldMap(topObj)) {
setMap = (Map) topObj;
} else {
setMap = new HashMap(); 创建一个Map
setMap.put(MAP_IDENTIFIER_KEY, ""); 向Map中存储一个值
push(setMap); 将Map压到栈顶
}
return setMap; 返回Map
}
private boolean shouldUseOldMap(Object topObj) {
//topObj instanceof Map : 判断栈顶的元素对象是否是一个Map集合
//((Map) topObj).get(MAP_IDENTIFIER_KEY) :判断该Map是否存在标志
return topObj instanceof Map && ((Map) topObj).get(MAP_IDENTIFIER_KEY) != null;
}
*
*
*
*/
valueStack.set("aaa", "AAA");
valueStack.push("xxxx");
valueStack.set("bbb", "BBB");
//通过valueStack的findValue(key/propertyName)方法取数据
return NONE;
}
}
struts2框架会将哪些数据自动保存到值栈中(重点)
1、引入struts2的标签<s:debug/> 该标签可以在页面中直观看到值栈的内部数据
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--引入Struts2标签--%>
<%@taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%--引入struts2的标签<s:debug/>--%>
<s:debug/>
</body>
</html>
2、struts2框架会自动将哪些数据存储到Root栈(重点)
将当前访问的Action对象自动压栈
作用:Action的get方法的属性值 可以通过ognl表达式方便获取
将当前模型对象自动压到栈顶
作用:模型对象压到栈顶 通过ognl表达式方便取出模型对象的属性值
3、struts2框架会自动将哪些数据存储到context栈(了解)
request对象
response对象
各种域数据
请求参数
... ...
维护本次请求的所有相关数据
public class ValueStackAction extends ActionSupport implements ModelDriven<User>{
private String username = "张三";
public String getUsername() {
return username;
}
private User user;
public User getUser() {
return user;
}
private List<User> userList;
public List<User> getUserList() {
return userList;
}
private int num = 3;
public int getNum() {
return num;
}
//测试自动保存数据到值栈
public String show(){
user = new User("tom", 18);
model.setName("传智播客");
model.setAge(15);
ActionContext.getContext().getValueStack().getContext().put("company", "itcast");
userList = new ArrayList<>();
User u1 = new User("小红", 18);
User u2 = new User("小兵", 38);
userList.add(u1);
userList.add(u2);
return SUCCESS;
}
}
如何取值栈的数据 (Ognl+struts2标签)(重点)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<s:debug/>
<h1>取出普通数据name</h1>
<s:property value="name"/>
<h1>取出user中的name</h1>
<s:property value="user.name"/>
<h1>取出context中的company数据</h1>
<s:property value="#company"/>
<h1>取出userList中的小兵</h1>
<s:property value="userList[1].name"/>
<h1>遍历userList</h1>
<!--
会将集合中的每一个元素对象 依次临时存储到栈顶
-->
<%-- <s:iterator value="userList">
<s:property value="name"/>==<s:property value="age"/><br/>
</s:iterator> --%>
<!--
var: 变量名称 临时使用var指定的字符串作为key在context栈存储一份
-->
<s:iterator value="userList" var="user">
<s:property value="#user.name"/>==<s:property value="#user.age"/><br/>
</s:iterator>
<!--
s:iterator可以模拟普通for循环
var:将循环数字 赋值给变量i
step:步长
status:封装是循环的状态数据 将状态对象以指定的名称存储到context栈
-->
<s:iterator begin="1" end="10" var="i" step="2" status="status">
<%-- <s:property value="#i"/><br/> --%>
<s:property value="#status.last"/>
</s:iterator>
<h1>struts2的if标签</h1>
<s:if test="num>10">
大于10
</s:if>
<s:elseif test="num==10">
等于10
</s:elseif>
<s:else>
小于10
</s:else>
<h1>el从值栈取值</h1>
${name }<br/>
${user.name }
</body>
</html>
注意:什么符号都不加 代表从Root栈取值,#代表直接从context栈取值。
为什么 EL也能访问值栈中的数据 (重点)(面试)
原因:StrutsRequestWrapper
${} 底层调用getAttribute方法
源码:
request = prepare.wrapRequest(request);
request = dispatcher.wrapRequest(request);
//BufferedReader reader = new BufferedReader(new FileReader());
StrutsRequestWrapper request = new StrutsRequestWrapper(request);
增强的是getAttribute方法:
public Object getAttribute(String key) {
//调用原来的request的方法
Object attribute = super.getAttribute(key); //从request域中获得值
if (attribute == null) {
attribute = stack.findValue(key);
}
return attribute;
}