贯穿MVC模型之间起到粘合剂作用的是数据。数据在View层成为了展示的内容,而在Controller层,成为了操作的载体,所以数据是整个MVC的核心。
数据在各个层次之间进行流转是一个不争的事实,而这种流转,也就会面临一些困境,这些困境,是由于数据在不同世界中的表现形式不同而造成的:
1. 数据在页面上是一个扁平的,不带数据类型的字符串,无论你的数据结构有多复杂,数据类型有多丰富,到了展示的时候,全都一视同仁的成为字符串在页面上展现出来。
2. 数据在Java世界中可以表现为丰富的数据结构和数据类型,你可以自行定义你喜欢的类,在类与类之间进行继承、嵌套。我们通常会把这种模型称之为复杂的对象树。
此时,如果数据在页面和Java世界中互相流转传递,就会显得不匹配。所以也就引出了几个需要解决的问题:
1. 当数据从View层传递到Controller层时,我们应该保证一个扁平而分散在各处的数据集合能以一定的规则设置到Java世界中的对象树中去。同时,能够聪明的进行由字符串类型到Java中各个类型的转化。
2. 当数据从Controller层传递到View层时,我们应该保证在View层能够以某些简易的规则对对象树进行访问。同时,在一定程度上控制对象树中的数据的显示格式。
如果我们稍微深入一些来思考这个问题,我们就会发现,解决数据由于表现形式的不同而发生流转不匹配的问题对我们来说其实并不陌生。同样的问题会发生在 Java世界与数据库世界中,面对这种对象与关系模型的不匹配,我们采用的解决方法是使用ORM框架,例如Hibernate,iBatis等等。那么现 在,在Web层同样也发生了不匹配,所以我们也需要使用一些工具来帮助我们解决问题。
为了解决数据从View层传递到Controller层时的不匹配性,Struts2采纳了XWork的OGNL方案。并且在OGNL的基础上,构建了OGNLValueStack的机制,从而比较完美的解决了数据流转中的不匹配性。
OGNL(Object Graph Navigation Language),是一种表达式语言。使用这种表达式语言,你可以通过某种表达式语法,存取Java对象树中的任意属性、调用Java对象树的方法、同 时能够自动实现必要的类型转化。如果我们把表达式看做是一个带有语义的字符串,那么OGNL无疑成为了这个语义字符串与Java对象之间沟通的桥梁。
h1. 基本概念
OGNL虽然简单,但其中却含有OGNL的两个最基本的概念:*表达式(expression)* 和 *上下文(context)*,我们先看*表达式*。
h3. 表达式
OGNL就是表达式!它能让我们用简洁直观的语法表达我们的想法,如同上面的例子一般。简洁直观就是表达式的最大优点!我们知道表达式总是有一个 结果,也就是说表达式总是会求值出一个结果,这个结果可能是一个字符串(如名称、组织机构代码证号等),或者是一个布尔值(如是否是VIP客户等),至于 这个结果要怎么使用,那就是我们自己来决定的了。
h3. 上下文(context)
表达式的概念,我相信很好理解,但什么是上下文(context)?简单来说上下文就是环境,表达式求值的环境!还是不理解吗?我们来看一个例子:
还是上面最后那个例子:
name
细心的你是否会问,这个表达式要取谁的姓名呢?OK,很好!这就是环境,"谁"就存在于环境之中,也就是存在上下文之中。对于不同的环境/上下文,相同的表达式会有不同的结果!而环境/上下文的实质是什么呢?就是一组带名称的对象集合。
h3. OGNL上下文概念详解
我们前面说上下文就是一组名称-对象对的集合,如下图所示就是一个简单的上下文:
user ---> User(name:"jack", ...) request ---> HttpServletRequest(header: ...)
那么在上面的环境中,我们可以有如下的OGNL表达式:
#user.name // 取用户的姓名 #user.age // 取用户年龄 #user.birthday // 取用户生日 #user.customer.name // 取用户所属客户的名称 #request.parameters // 取请求参数
请注意上面表达式中的"#user"和"#request"的用法,"#"表示访问环境/上下文中的对象。
现在可以很方便地访问环境中的对象了,那么如果你比较懒惰的话(记住:在程序员群体,懒惰是褒义词!),你是否觉得访问用户的姓名,年龄,生日, 等等其它属性如果全部要使用"#user"来访问会不会太麻烦了呢?OK,ONGL的设计者早就考虑了这个问题,我们可以指定user为环境中的特权对 象,访问该对象可以不需要使用#user的方式,如下所示代码与上面的完全等价,当然,前提是要预先指定user为特权对象:
name // 取用户的姓名 age // 取用户年龄 birthday // 取用户生日 customer.name // 取用户所属客户的名称 #request.parameters // 取请求参数
我们上面所说的"特权对象"在OGNL中称为"根对象"(root)
h3. 小结
综上所述,理解OGNL表达式的关键是理解其上下文的概念,因为OGNL的上下文概念中引入了"根对象"的概念,所以初学者往往会在这里迷失方向。
慢着,事情还未至此结束!struts2对OGNL中的上下文的概念又定义了新的含义,且听我慢慢道来!
h3. struts2中的OGNL上下文
struts2对OGNL上下文的概念又做了进一步扩充,在struts2中,OGNL上下文通常如下所示:
|
|--request
|
|--application
|
context map---|--OgnlValueStack(root) [ user, action, OgnlUtil, ... ]
|
|--session
|
|--attr
|
|--parameters
我们可以使用"#requet"访问HttpServletRequest对象, "#session"访问HttpSession对象,但请注意"根对象"是什么?是ValueStack!
那么ValueStack是什么?值栈。也就是一组对象的堆栈。也就是说,在struts2中,根对象不是我们通常的一个对象,而是一组对象。我 们可以push新的对象到值栈中,也可以弹出值栈的栈顶对象。如上图所示,假设我们将user对象push到值栈中,那么如下的表达式将与之前我们见过的 表达式一样,具有相同的结果:
name // 取用户的姓名 age // 取用户年龄 birthday // 取用户生日 customer.name // 取用户所属客户的名称 #request.parameters // 取请求参数
也就是说,我们使用name这个表达式的时候,ONGL会取"根对象"的name属性,但现在根对象是ValueStack!那么访问 ValueStack的name属性意味着什么呢?这意味着: ValueStack会先查看栈顶元素是否有name属性,如果有就返回该属性值,否则取出栈顶下的元素,继续查看,直到栈底为止。
以上就是OGNL表达式的核心概念,你理解了吗?下一步,你需要了进一步了解OGNL的语法,以发掘其更强大的功能!