SSH框架中不为人知的细节(二)

Struts2之ValueStack

    上一节在将阐述ModelDriven的机制时,常常提到一个名词ValueStack。也许你会毫不犹豫脱口而出,不就是值栈吗?对,就是它,那你知道Struts为什么需要引入它?它是如何工作的?它和OGNL有何私情?如果你对以上问题的答案很模糊,但是又确实想知道答案,那么本文将带你去看看ValueStack的世界。

Why ValueStack?

    说Struts就不得不提MVC,所有的请求都是基于页面(View)提交,页面是数据最初的载体,然后提交给Controller,由Controller将数据做第一次处理,然后交给业务层做进一步的处理。下面,我们还是通过代码直观的体会一下。

JSP页面伪码:

<form action="xxx/user-add.action" method="post">
    username:<input type="text" name="username" />
    age:<input type="text" name="age" />
    groupName:<input type="text" name="group.name" />
    <input type="submit" name="submit" value="添加" />
</form>

VO伪码:

public class UserVO {
    private String username;
    private int age;
    private Group group;

    //getter and setter
    //...
}

public class Group {
    private String name;
    
    //getter and setter
    //...
}

Action伪码:

public class UserAction implements ModelDriven {

    private User user = new User();

    public String addUser() {
        //相应的业务逻辑
    }

    public String queryUser() {
        //返回user对象
    }

    @Override
    public Object getModel() {
        return user;
    }
}

    通过以上代码,可以看出来, 数据在View层是扁平的,没有数据类型之分,均为字符串。但是在Java中,数据有各种数据类型(age -> int),丰富的数据结构 (group -> Group)。所以数据由页面向Java流转时,就会出现类型如何匹配的问题。

    为了解决以上的问题,Struts2采纳了XWork的OGNL(后面会专门讲解)做为解决方案,在OGNL基础上构建了OGNLValueStack机制,完美的实现了数据由View到Controller的匹配问题


ValueStack如何运转?

    大家一定对下面这段代码不陌生:

username:<s:property value="username" />
age:<s:property value="age" />
groupName:<s:property value="group.name" />

    使用Struts2的Tag通过OGNL取值,在上一节我们讲到,user对象最终被保存到了ValueStack中。如果你有兴趣阅读struts2的源码时,你会发现ValueStack只是一个接口,而Struts2使用了OgnlValueStack作为其默认实现。

ValueStack接口中声明了getRoot()这样一个接口:

/**
 * Get the CompoundRoot which holds the objects pushed onto the stack
 *
 * @return the root
 */
public abstract CompoundRoot getRoot();

看下CompoundRoot的实现:

public class CompoundRoot extends ArrayList {

    //...省略一些其他实现
    
    public Object peek() {
        return get(0);
    }

    public Object pop() {
        return remove(0);
    }

    public void push(Object o) {
        add(0, o);
    }
}

    学过数据结构的一眼就看出来,CompoundRoot具有栈的特性:先进后出。所以在s:property标签中的OGNL表达式,最终会交给ValueStack来解析。username就是一个OGNL表达式,意思是调用root对象的getUsername()方法。Struts2将自动搜索CompoundRoot中有哪些元素(从第0个元素开始搜索),检测这些元素是否有getUsername()方法,如果第0个元素没有getUsername()方法,将继续搜索第1、2、3……个元素是否有getUsername()方法。很明显,user对象有这个方法,所以就可以显示username的值。既然CompoundRoot维护的是一个栈结构,那么如果有多个元素都含有getUsername()方法的话,那么<s:property value="username" />只会得到最后一个压入栈的元素对应的username的值。

    

ValueStack & OGNL

    在讲ValueStack的过程中,OGNL一直伴随左右,那么什么是OGNL呢?官方的解释就是:Object Graph Navigation Language,是一种表达式语言。使用这种表达式语言,你可以通过某种表达式语法,存取Java对象树中的任意属性、调用Java对象树的方法、同时能够自动实现必要的类型转化。如果我们把表达式看做是一个带有语义的字符串,那么OGNL无疑成为了这个语义字符串与Java对象之间沟通的桥梁。下面通过一个简单的程序看看OGNL的用法。

public class OgnlTest {
    
    @Test
    public void test01() {

        //创建root对象
        User u = new User();
        u.setUsername("javer");
        u.setAge(18);
        Group g = new Group();
        g.setName("group01");
        u.setGroup(g);
        
        //创建context
        Map ctx = new HashMap();
        ctx.put("context", "get from context:");
        
        //直接取root对象
        Object username = Ognl.getValue(Ognl.parseExpression("username"), u);
        AssertEquals("javer", username); //true

        //从上下文取值
        Object value = Ognl.getValue(Ognl.parseExpression("#context", ctx, u):
        AssertEquals("get from context:", value); //true

        //从上下文取root对象
        Object value = Ognl.getValue(Ognl.parseExpression("#context + username", ctx, u);
        AssertEquals("get from context:javer", value);
    }
}

    上面这个例子其实涵盖了OGNL的三大要点:

1. Expression:整个OGNL的核心,所有的OGNL操作都是针对表达式的解析后进行的。表达式会规定此次OGNL操作到底要干什么

2. Root Object:OGNL的操作对象,即指定到底“对谁干”

3. Context:OGNL的内部,所有的操作都会在一个特定的环境中运行,这个环境就是OGNL的上下文环境,将规定OGNL的操作“在哪里干”

    回过头来再看看ValueStack,值栈中的数据,也是分两个部分存放:root和context。如果某个OGNL表达式被传递给ValueStack(即调用ValueStack的setValue或findValue方法),而表达式中包含有对root对象的访问操作,ValueStack将依次从栈顶往栈底搜索CompoundRoot对象中所包含的对象,看哪个对象具有相应的属性,找到之后,立刻返回。

    所以ValueStack就是对OGNL进行了封装,增强了部分功能。说白了,ValueStack其实就是OGNL的一个扩展,在OGNL只支持一个root的基础上扩展到了支持多个root对象。说到这里,可能你也恍然 大悟,Action就是一个root对象,在不使用ModelDriven的时候,页面上的元素不就是和Action对象中的成员变量一一对应吗!这下二者的关系你心中也有数了吧。


你可能感兴趣的:(mvc,struts2,Ognl,ValueStack)