什么是值栈
之前web阶段,在servlet里面进行操作,把数据放到域对象里面,在页面中使用el表达式获取到。域对象在一定范围内,存值和取值。
在struts2里面提供了本身的一种存储机制,类似于域对象,是值栈,可以存值和取值。
- 在action里面把数据放到值栈里面,在页面中获取到值栈的数据。
注意:对于存值,你可以使用servlet中的三种域对象,使用这三种域对象都可以在jsp页面中获取到。同时你使用struts2里面的存储机制同样能够实现上面的效果,但是并不是一定要你使用struts2里面的存储方法。目的要让你知道struts2也能够办到同样存值取值的操作。只是你说会struts2 但是你不会值栈,这是说不过去的!!!
servlet和action的区别:
Servlet:
默认在第一次访问的时候创建,只创建一次,是一个单例对象!!
Action:
一样是访问的时候创建对象,每次访问action的时候都会创建新的action对象,创建多次,是一个多实例对象!!
值栈的存储位置:
- 每次访问action的时候都会创建action对象。
- 在每个action对象里面都会有一个值栈对象。(注意:每个action对象只有一个)
获取值栈
获取值栈对象有多种方式!
- 常用的方式:使用ActionContext对象里面的方法(getValueStack())获取值栈对象。
目的验证在同一个action值栈只有一个:
public class pr_action{
public String execute(){
ActionContext context=ActionContext.getContext();
ValueStack vs1=context.getValueStack();
ValueStack vs2=context.getValueStack();
System.out.println(vs1==vs2);
}
}
输出结果:
true
值栈内部结构
栈:先进后出!
最上面是栈顶的元素,向栈里面放数据的操作叫做压栈。
所以,你没有猜错,值栈也是用的这种数据结构!但是值栈分为两个部分,root和context,root就是栈的数据结构,同时context也是
root专业叫做ObjectStack(对象栈)
context专业叫做ContextMap(Map栈),Map类型的栈。(在我们访问里面的对象的时候,会通过出栈的方式取东西,效率比较低,一般我们不会用)
值栈分为两个部分:
- 第一个部分 root ,平常我们用的值栈就是操作root里面的内容,很少去操作context。结构是List集合。
可以通过ctrl+shift+t搜索类,来查看它的父类。
很惊奇你会发现root对象类型继承了ArrayList这个类
- 第二部分 context ,结构是Map集合
下面来讲讲context这个对象:
context里面存储的都是些固定的值,有以下几个:
key————value
request—>最底层是request(HttpServletRequest),但是在这里的request是RequestMap类型的。如果你在HttpServletRequest类型中赋了值,那么在RequestMap中照样能够读取到。
session—>HttpSession对象引用
application—>ServletContext对象引用
parameters—>传递相关的参数
attr—>使用attr操作,能够获取域对象里面的值,获取域范围最小里面的值。
要想查看到值栈的结构可以用调试(debug)的方法!
通过struts2的标签<s:debug></s:debug>
,我们可以很清楚的看到值栈确实分为两个部分,一个root,一个context,上面我们已经讲解了context的存储内容
,下面我们就来讲讲root的存储内容:
我们已经了解到root是一个栈的存储结构。下面是root默认存储的内容:
action对象引用
DefaultTextProvider
也就是说在root的栈顶还存储着action的引用,为什么会这个存储呢??
其实它只是为了能够在值栈里面取出action,能够在action里面取出值栈,仅此而已!
向值栈里面放数据
向值栈里面放数据时,其实存储的位置是在root域里面
向值栈放数据有多种方式,往往我们只用其中一种
- 第一种 获取值栈对象,调用值栈对象里面的 set 方法
ValueStack stack=ActionContext.getContext().getValueStack();
stack.set("username","FireLang");
在用set方法添加值栈数据之后,会在root栈顶多一个HashMap对象
- 第二种 获取值栈对象,调用值栈对象里面的 push 方法
调用push之后,就会在root栈顶放入一个String类型的对象!
- 第三种 在action定义变量,生成变量的get方法(主要)
public class pr_action{
private String name;
public String getName(){
return name;
}
public String execute(){
name="FireLang";
return "success";
}
}
在用第三种方法之后,struts2并不会在值栈root的栈顶放入新的对象,它的存储路径还是存储在action里面,所以这就起到了资源的合理应用,当想要获取name属性的时候,就会在值栈里面调用action的getName方法。这也就是为什么会在值栈里面存储action的原因了。
向值栈中放对象
实现步骤:
第一步:定义对象变量
第二步:生成变量的get方法
第三步:在执行的方法里面向对象中设置值
向值栈中放List对象
第一步:定义List集合变量
第二步:生成变量的get方法
第三步:在执行的方法里面向List集合设置值
action的代码:
public class Pr_fangList {
private List<User> lu;
public String execute(){
lu=new ArrayList<User>();
User u1=new User();
u1.setName("胡艺宝");
u1.setPassword("123");
lu.add(u1);
User u2=new User();
u2.setName("胡家源");
u2.setPassword("456");
lu.add(u2);
System.out.println(lu);
return "success";
}
public List<User> getLu() {
return lu;
}
}
jsp中代码:
<!-- 文章截止到目前还没有说到el表达式为什么能够取到值栈里面的数据,在文章后续会解释的 -->
${lu[0].name }
${lu[0].password }<br>
<hr><br>
${lu[1].name }
${lu[1].password }
<s:debug></s:debug>
从值栈的root里面取数据
使用struts2的标签+ognl表达式获取值栈数据
<s:property value="ognl表达式"/>
获取字符串
步骤:
服务端代码:
//服务端代码是为了让字段被存入ValueStack中
//字段的前提条件是必须设置get方法
public class Pr_getString {
private String name;
public String getName() {
return name;
}
public String execute(){
name="胡艺宝";
return "success";
}
}
客户端jsp代码:
<s:property value="name"/><!-- 这里的name是ognl表达式。表示获取action中的name字段值,必须要写get方法,因为字段读或者写的功能按照规定,都必须通过读或者写方法来给变量赋值 -->
获取对象
aciton中的代码:
//再次强调必须要get方法。
public class Pr_getUserObj {
private User us;
public User getUs() {
return us;
}
public String execute(){
us=new User();
us.setName("胡艺宝");
us.setPassword("FireLang");
return "success";
}
}
jsp中的代码:
<s:property value="us.name" /><br><!-- value中的值是ognl表达式 -->
<s:property value="us.password" /><!-- 获取到us对象后,再获取us中的name属性和password属性,再次强调获取字段基本上都是按照规定通过get和set方法进行操作! -->
获取List集合
通过ognl+struts标签获取List集合共有三种方式
服务端代码:
public class Pr_getList {
private List<User> usl=null;
public List<User> getUsl() {
return usl;
}
public String execute(){
usl=new ArrayList<User>();
User tempUser=new User("胡艺宝", "123465");
usl.add(tempUser);
return "success";
}
}
第一种:
客户端代码:
//这种代码非常不好,在很多时候你永远不可能知道服务端传来的List里面到底有多少参数。
<s:property value="usl[0].name"/>
<s:property value="usl[0].password"/>
第二种方式:
类似jstl中的foreach标签
<s:iterator value="usl">
<s:property value="name"/>
<s:property value="password"/>
<br><hr><br>
</s:iterator>
第三种方式:
/*第三种方式较第二种方式多加了一个var,根本区别就是iterator把遍历出来的值放进了值栈的第二部分空间context,contex因为是Map结构的所以要加上一个键作为取值方式,也就是var的值作为context的键,其实这种方式算是一种优化,不用在root中去拿值了。而第二种方式还会到root里面去拿值。速度没有在context中的快*/
<s:iterator value="usl" var="singleus">
<s:property value="#singleus.name"/>
<s:property value="#singleus.password"/>
</s:iterator>
set方法和push方法的取值(会用)
set方法的取值
//服务端代码:
public class Pr_getUserSet {
private User us;
public String execute(){
us=new User("胡艺宝","789");
ActionContext.getContext().getValueStack().set("us", us);
ActionContext.getContext().getValueStack().set("lang", "FireLang");;
return "success";
}
}
<!-- 客户端代码: -->
<!-- 直接从root域里面取值 -->
<s:property value="us"/><br><br>
<s:property value="lang"/>
//运行结果:
User [name=胡艺宝, password=789]
FireLang
push方法取值
//服务器端代码:
//这里要注意的是push方法是直接把数据存放在root中的。不像set一样可以通过key来取值。
//push的取值方法有点特殊,是通过直接把栈顶元素取出来的。
public class Pr_getUserSet {
private User us;
public String execute(){
us=new User("胡艺宝","789");
ActionContext.getContext().getValueStack().push(us);
ActionContext.getContext().getValueStack().push("FireLang");
return "success";
}
}
<!-- 客户端jsp代码 -->
<s:property value="[0].top"/>//取第一个
<s:property value="[1].top"/>//取第二个,这里的top是root的域实体对象名称,也就是List对象的名称
运行结果:
FireLang User [name=胡艺宝, password=789]
增强一个类常用的方式:装饰者模式,动态代理,继承
用EL表达式取值:
el取值能够获取到action里面的值,具体原理就是,它重写的request,进行了request域的增强,里面进行了以下操作:
el取值时,如果request域里面能够找到目标值,那么就把值返回到页面。如果在request域里面不能够取到目标值,那么就通过值栈获取。
ActionContext.getContext().getValueStack().findValue(“key”);如果查找到值就返回数据。这里的request是通过HttpServletRequestWrapper重写过。
所以在el表达式获取Action里面存取的值的时候效率没有通过Struts标签来的快。推荐用struts标签和ongl来获取Action里面的数据。
el在获取Action里面的值时,action里面的字段也必须提供get方法。否则无法获取到值。
//服务端代码:
public class Pr_getList {
private List<User> usl=null;
public List<User> getUsl() {
return usl;
}
public String execute(){
usl=new ArrayList<User>();
User tempUser=new User("胡艺宝", "123465");
usl.add(tempUser);
return "success";
}
}
<!-- 客户端jsp代码: -->
<!-- 在使用jstl标签以前要先导入jstl标签库,这里使用的jstl标签库是1.2版本 -->
<c:forEach items="${usl }" var="temp">
${temp.name }<br>
${temp.password }
</c:forEach>
//这个是增强request的源码:
ActionContext ctx = ActionContext.getContext();
Object attribute = super.getAttribute(key);
if (ctx != null && attribute == null) {
boolean alreadyIn = isTrue((Boolean) ctx.get(REQUEST_WRAPPER_GET_ATTRIBUTE));
// note: we don't let # come through or else a request for
// #attr.foo or #request.foo could cause an endless loop
if (!alreadyIn && !key.contains("#")) {
try {
// If not found, then try the ValueStack
ctx.put(REQUEST_WRAPPER_GET_ATTRIBUTE, Boolean.TRUE);
ValueStack stack = ctx.getValueStack();
if (stack != null) {
attribute = stack.findValue(key);//这就是el能够获取到值栈里面的关键
}
} finally {
ctx.put(REQUEST_WRAPPER_GET_ATTRIBUTE, Boolean.FALSE);
}
}
}
return attribute;
ognl两个符号的使用#号和%的使用
使用#来获取context中的数据
//服务端的代码:
public class Pr_getRequestByContext {
private User us;
public User getUs() {
return us;
}
public String execute(){
us=new User("胡艺宝", "123456789");
((RequestMap)ActionContext.getContext().getValueStack().getContext().get("request")).put("user", us);//把数据保存到context的request中
//其中你也可以通过ActionContext.getContext().get("request")获取到的效果和上面的一样。
System.out.println(ServletActionContext.getRequest().getAttribute("user"));//验证是否能够在HttpServletRequest中获取到user,事实证明能够获取到。猜测RequestMap的最底层依赖了HttpServletRequest
return "success";
}
}
<!-- 这个是jsp客户端的代码 -->
<s:property value="#request.user"/>
运行后能够成功获取user数据
%号的使用
场景:在struts2表单标签里面使用ognl表达式,如果直接在struts2表单标签里面使用ognl表达式会不识别,只有使用%号之后才会识别。
//服务端代码就用上面那个
<!-- 客户端jsp代码,成功运行输出后的代码: -->
<s:textfield name="username" value="%{#request.user.name}"></s:textfield>
OK!!!完成!!