值栈详解(ValueStack)

什么是值栈

之前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>

为什么EL表达式能够获取值栈里面的数据

从值栈的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!!!完成!!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值