首先让我们花费1分钟的时间来简单思考一个问题,MVC这3者之间,到底是通过什么真正融合起来的?
有人说是Controller,因为它是核心控制器,没有Controller,MVC就无从谈起,失去了职责划分的原本初衷。也有人说是View,因为所有的需求都是页面驱动的,没有页面,就没有请求,没有请求,也谈不上控制器和数据模型。
个人观点:贯穿MVC模型之间起到粘合剂作用的是数据。数据在View层成为了展示的内容,而在Controller层,成为了操作的载体,所以数据是整个MVC的核心。
流转的数据
无论MVC三者之间的粘合剂到底是什么,数据在各个层次之间进行流转是一个不争的事实。而这种流转,也就会面临一些困境,这些困境,是由于数据在不同世界中的表现形式不同而造成的:
1. 数据在页面上是一个扁平的,不带数据类型的字符串,无论你的数据结构有多复杂,数据类型有多丰富,到了展示的时候,全都一视同仁的成为字符串在页面上展现出来。
2. 数据在Java世界中可以表现为丰富的数据结构和数据类型,你可以自行定义你喜欢的类,在类与类之间进行继承、嵌套。我们通常会把这种模型称之为复杂的对象树。
此时,如果数据在页面和Java世界中互相流转传递,就会显得不匹配。所以也就引出了几个需要解决的问题:
1. 当数据从View层传递到Controller层时,我们应该保证一个扁平而分散在各处的数据集合能以一定的规则设置到Java世界中的对象树中去。同时,能够聪明的进行由字符串类型到Java中各个类型的转化。
2. 当数据从Controller层传递到View层时,我们应该保证在View层能够以某些简易的规则对对象树进行访问。同时,在一定程度上控制对象树中的数据的显示格式。
如果我们稍微深入一些来思考这个问题,我们就会发现,解决数据由于表现形式的不同而发生流转不匹配的问题对我们来说其实并不陌生。同样的问题会发生在Java世界与数据库世界中,面对这种对象与关系模型的不匹配,我们采用的解决方法是使用ORM框架,例如Hibernate,iBatis等等。那么现在,在Web层同样也发生了不匹配,所以我们也需要使用一些工具来帮助我们解决问题。
在这里,我们主要讨论的,是数据从View层传递到Controller层时的解决方案,而数据从Controller层传递到View层的解决方案,我们将在Struts2的Result章节重点讨论。
OGNL —— 完美的催化剂
为了解决数据从View层传递到Controller层时的不匹配性,Struts2采纳了XWork的OGNL方案。并且在OGNL的基础上,构建了OGNLValueStack的机制,从而比较完美的解决了数据流转中的不匹配性。
OGNL(Object Graph Navigation Language),是一种表达式语言。使用这种表达式语言,你可以通过某种表达式语法,存取Java对象树中的任意属性、调用Java对象树的方法、同时能够自动实现必要的类型转化。如果我们把表达式看做是一个带有语义的字符串,那么OGNL无疑成为了这个语义字符串与Java对象之间沟通的桥梁。
如何使用OGNL
让我们先研究一下OGNL的API,他来自于Ognl的静态方法:
- /**
- * Evaluates the given OGNL expression tree to extract a value from the given root
- * object. The default context is set for the given context and root via
- * <CODE>addDefaultContext()</CODE>.
- *
- * @param tree the OGNL expression tree to evaluate, as returned by parseExpression()
- * @param context the naming context for the evaluation
- * @param root the root object for the OGNL expression
- * @return the result of evaluating the expression
- * @throws MethodFailedException if the expression called a method which failed
- * @throws NoSuchPropertyException if the expression referred to a nonexistent property
- * @throws InappropriateExpressionException if the expression can't be used in this context
- * @throws OgnlException if there is a pathological environmental problem
- */
- public static Object getValue( Object tree, Map context, Object root ) throws OgnlException;
- /**
- * Evaluates the given OGNL expression tree to insert a value into the object graph
- * rooted at the given root object. The default context is set for the given
- * context and root via <CODE>addDefaultContext()</CODE>.
- *
- * @param tree the OGNL expression tree to evaluate, as returned by parseExpression()
- * @param context the naming context for the evaluation
- * @param root the root object for the OGNL expression
- * @param value the value to insert into the object graph
- * @throws MethodFailedException if the expression called a method which failed
- * @throws NoSuchPropertyException if the expression referred to a nonexistent property
- * @throws InappropriateExpressionException if the expression can't be used in this context
- * @throws OgnlException if there is a pathological environmental problem
- */
- public static void setValue( Object tree, Map context, Object root, Object value ) throws OgnlException
我们可以看到,OGNL的API其实相当简单,你可以通过传递三个参数来实现OGNL的一切操作。而这三个参数,被我称为OGNL的三要素。
那么运用这个API,我们能干点什么呢?跑个测试看看结果:
- /**
- * @author Downpour
- */
- public class User {
- private Integer id;
- private String name;
- private Department department = new Department();
- public User() {
- }
- // setter and getters
- }
- //=========================================================================
- /**
- * @author Downpour
- */
- public class Department {
- private Integer id;
- private String name;
- public Department() {
- }
- // setter and getters
- }
- //=========================================================================
- /**
- * @author Downpour
- */
- public class OGNLTestCase extends TestCase {
- /**
- *
- * @throws Exception
- */
- @SuppressWarnings("unchecked")
- @Test
- public void testGetValue() throws Exception {
- // Create root object
- User user = new User();
- user.setId(1);
- user.setName("downpour");
- // Create context
- Map context = new HashMap();
- context.put("introduction","My name is ");
- // Test to directly get value from root object, with no context
- Object name = Ognl.getValue(Ognl.parseExpression("name"), user);
- assertEquals("downpour",name);
- // Test to get value(parameter) from context
- Object contextValue = Ognl.getValue(Ognl.parseExpression("#introduction"), context, user);
- assertEquals("My name is ", contextValue);
- // Test to get value and parameter from root object and context
- Object hello = Ognl.getValue(Ognl.parseExpression("#introduction + name"), context, user);
- assertEquals("My name is downpour",hello);
- }
- /**
- *
- * @throws Exception
- */
- @SuppressWarnings("unchecked")
- @Test
- public void testSetValue() throws Exception {
- // Create root object
- User user = new User();
- user.setId(1);
- user.setName("downpour");
- // Set value according to the expression
- Ognl.setValue("department.name", user, "dev");
- assertEquals("dev", user.getDepartment().getName());
- }