郑重申明:包括本文在内的很多技术文章,大多出自山外高人,而非Fans。
Fans暂时没有能力写作优秀的技术文章,Fans只是转载、浓缩、加入部分自己的代码而已。
1.数据流转的困境
什么是数据流转的困境?数据为什么会流转?数据的流转又会遇到什么困境?在回答这些问题之前,我们必须首先了解一个事实:
结论:有一股神秘的力量在MVC的各个模块中进行流转,并且它在不同的MVC层次中表现出不同的形态和状态。
我们不妨用一段简单的Struts2程序来证明这一点。这里依然选择Registration作为业务场景,源代码如下:
代码清单1 User.java
public class User{
private String name;
private String password;
private Date birthday;
//省略了setter和getter方法
}
代码清单2 user.jsp
<form method="post" action="registration.action">
user name:<input type="text" name="user.name" value="downpour"/>
password:<input type="password" name="user.password" value="pass"/>
birthday:<input type="text" name="user.birthday" value="1989-03-23" />
<input type="submit" value="submit"/>
</form>
代码清单3 UserAction.java
public class UserAction{
private User user;
public String execute(){
// 可以直接在这里使用user对象,因为它已经被作为传入的参数了
return "success";
}
//省略了setter和getter方法
}
这是一个简单的Struts2入门程序。仔细观察其中的运行细节就会发现,User是贯穿整个流转过程的数据载体,鉴于它的特殊作用,我们通常将User称之为数据模型(Model)。我们发现,User这个数据模型在不同的MVC模块中,表现出不同的形式:
View层--表现层为字符串展现
在这里,View层的数据模型讲遵循Http协议,因而它没有数据的概念。所有数据在页面上的表现都是一个个扁平的、不带数据类型的字符串,无论数据结构有多么复杂,数据类型有多丰富,到了展现的时候,全都一视同仁地当作字符串在页面上展现出来。数据在传递时,任何数据都被当作字符串或者字符串的数组来进行。
Controller层--表现为Java对象
在控制层,数据模型遵循Java的语法和数据结构。所有数据载体在Java世界中可以表现为丰富的数据结构和数据类型,你可以自定义喜欢的类,在类与类之间进行继承、嵌套。我们通常会把这种模型称之为对象树。数据在传递时,将以对象的形式进行。
结论:数据在不同的MVC层次上,扮演的角色和表现形式不同。这是由于Http协议与Java的面向对象性之间的不匹配造成的。
如果我们要数据在View层(页面)和Java世界中互相流转传递,就会在“字符串”与“对象树”之间存在不匹配性。这个不匹配性源于Web是一个“弱类型”的平台,Web的目标是展示类容,而Java却是具有丰富数据类型的“强类型”的平台,目标是处理复杂的逻辑。同一个对象,在“弱类型”的平台和一个“强类型”的平台之间交互,就必须有一个非常重要的“翻译”角色解决这种“不匹配”。这个角色,就是我们所说的表达式引擎,它充当着桥梁作用,保证数据能够顺利地在MVC的各个层次进行流转。
Java与其它数据形式的匹配
在我们的日常开发中,不仅要面对Java世界的对象,同时还要面对各种其它的数据元素表现形式(XML、HTML、关系数据库、JSON等)。这种情况下,我们总是采用一些工具来帮助解决不匹配的问题。例如,我们使用Hibernate或者myBatis这样的持久层框架来处理Java对象与关系型数据库的匹配。
2.数据访问的困境
数据模型(Model)除了表现出流动性的特征外,我们同时更加关心数据的内容。在Web环境中,我们时不时会需要在MVC的任何执行层次对数据的内容进行访问。由于数据模型在MVC的不同不同层次的表现形式以及遵循的协议都完全不同,因而在MVC的不同层次,我们进行数据访问的具体方式也不同。
数据访问的困境,主要还是来源于数据模型在某些层次的展现缺乏足够的表现力。例如,在视图层,既没有数据类型的概念,野咩野数据结构的概念。无论数据自身的结构有多复杂,一旦对外展现,都最终转化为统一的字符串形式。在这种情况下,我们就需要建立一种符号化的规则,使之与复杂而丰富的Java类型对应起来,这种规则就是表达式引擎的雏形。
在数据流转的过程中,我们可以看到两个截然不同的方向:
从View层到Controller层
这个方向的数据流转所强调的,是能够保证一个扁平而分散的数据能以一定的规则设置到Java世界的对象树中去。同时,能够聪明地进行由字符串到Java中各个类型的转化。
从Controller层到View层
这个方向的数据流转所强调的,是保证在View层能够以某些简易的规则对Java的对象树进行访问。同时,在一定程度上控制对象树中的数据的显示格式。我们这里所说的数据访问,其实指的是数据从Controller层流转到View层的过程。在这个过程中,表达式引擎所起的作用,就是建立起访问规则与Java对象树之间的联系。在这个方面有不少成熟的方案,如JSTL、模版语言等等,但无论是什么样的解决方案,其实现的基础都是一致的,那就是表达式引擎。
3.表达式引擎
从我们对所遇到的Web开发中的困境的讨论中,我们已经看到了在Web开发中引入表达式引擎的重要性。那么表达式引擎又是如何工作的呢?页面上的参数到底是如何与我们定义的Java类关联起来并实现交互的呢?我们以刚刚的Registration例子为基础,分析一下它们的存在形式,如下表所示。
页面参数与Action中User类的映射关系
页面中的参数名 页面中的参数值 User类中的字段(类型) User类中的字段值
user.name Downpour name(String) Downpour
user.password Pass password(String) Pass
user.birthday 1982-03-23 birthday(java.util.date) 1982-03-23
实际上,第2列和第4列的内容是相同的,它们在本质上都是“值”。唯一的区别在于,第2列的值的存在空间是页面,因而无法为其定义所谓的数据类型,它们看上去都表现为字符串形式,而第4列的值的存在空间是Java类,此时,可以为之定义我们所需要的任何数据类型。
我们重点来关注一下第1列和第3列。如果把它们单独列出来,实际上我们所实现的是一种转化,这种转化存在于一个规则化的表达式和Java对象之间:
user.name---user实例的name字段
user.password---user实例的password字段
此时,我们就需要引入一个概念---表达式引擎。它的作用就是帮助我们完成这种规则化的表达式与Java对象之间的互相转化。从上面的例子可以看出,表达式引擎在其中起到了非常关键的穿针引线的作用。
结论:表达式引擎在Web开发中能够完成规则化字符串表达式与Java对象之间的互相转化,
因而它成为架起MVC各个模块之间数据沟通的桥梁。
发挥我们的主观能动性来思考一下,数据在交互的过程中到底会遇到什么样的困境,这些困境讲成为我们选择和使用表达式引擎的依据:
表达式引擎应该能处理表达式与对象之间的映射关系,这种映射关系应是双向的。
表达式引擎应该能支持丰富多样的表达式语法计算。
表达式引擎应该能支持必要的数据类型的转换。
在Java世界中,有许多优秀的表达式引擎,比如OGNL。至于OGNL在内部实现机制和设计原理上有哪些亮点,还望读者参考OGNL自行研究。