OGNL —— 数据运转的催化剂 (及在Struts2中使用OGNL)

好久没写文章了,转一篇吧。不错的。

Spring是用IoC来实现AOP,而Xwork是用AOP来实现IoC,整个Xwork的架构就是完全基于AOP之上的 。

之所以说Spring是用IoC来实现AOP这个是spring的核心,而最具代表性的应该是其通过配置文件来实现IoC的控制思想;而Xwork是用AOP来实现IoC,最具代表性的就是其interceptor。

 

如果想学struts2. 请看下面的博客
http://downpour.javaeye.com/
中的“忘记李刚,一步一步跟我学Struts2”。
我本人确实看完了李刚写的strust2权威指南一书。 然后再看了downpour写的文章。感觉downpour写的不错,有深入了解了。

 

 

 

首先让我们花费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的静态方法:

Java代码


我们可以看到,OGNL的API其实相当简单,你可以通过传递三个参数来实现OGNL的一切操作。而这三个参数,被我称为OGNL的三要素。

那么运用这个API,我们能干点什么呢?跑个测试看看结果:

Java代码
  1. /**  
  2.  * @author Downpour  
  3.  */  
  4. public class User {   
  5.        
  6.     private Integer id;   
  7.        
  8.     private String name;   
  9.        
  10.     private Department department = new Department();   
  11.        
  12.     public User() {   
  13.            
  14.     }   
  15.            
  16.         // setter and getters   
  17. }   
  18.   
  19. //=========================================================================   
  20.   
  21. /**  
  22.  * @author Downpour  
  23.  */  
  24. public class Department {   
  25.        
  26.     private Integer id;   
  27.        
  28.     private String name;   
  29.        
  30.     public Department() {   
  31.            
  32.     }   
  33.            
  34.         // setter and getters   
  35. }   
  36.   
  37. //=========================================================================   
  38.   
  39. /**  
  40.  * @author Downpour  
  41.  */  
  42. public class OGNLTestCase extends TestCase {   
  43.        
  44.     /**  
  45.      *   
  46.      * @throws Exception  
  47.      */  
  48.     @SuppressWarnings("unchecked")   
  49.     @Test  
  50.     public void testGetValue() throws Exception {   
  51.            
  52.         // Create root object   
  53.         User user = new User();   
  54.         user.setId(1);   
  55.         user.setName("downpour");   
  56.   
  57.         // Create context   
  58.         Map context = new HashMap();   
  59.         context.put("introduction","My name is ");   
  60.            
  61.         // Test to directly get value from root object, with no context   
  62.         Object name = Ognl.getValue(Ognl.parseExpression("name"), user);   
  63.         assertEquals("downpour",name);   
  64.            
  65.         // Test to get value(parameter) from context   
  66.         Object contextValue = Ognl.getValue(Ognl.parseExpression("#introduction"), context, user);   
  67.         assertEquals("My name is ", contextValue);   
  68.            
  69.         // Test to get value and parameter from root object and context   
  70.         Object hello = Ognl.getValue(Ognl.parseExpression("#introduction + name"), context, user);   
  71.         assertEquals("My name is downpour",hello);   
  72.                        
  73.     }   
  74.   
  75.     /**  
  76.      *   
  77.      * @throws Exception  
  78.      */  
  79.     @SuppressWarnings("unchecked")   
  80.     @Test  
  81.     public void testSetValue() throws Exception {   
  82.            
  83.         // Create root object   
  84.         User user = new User();   
  85.         user.setId(1);   
  86.         user.setName("downpour");   
  87.            
  88.                 // Set value according to the expression   
  89.         Ognl.setValue("department.name", user, "dev");   
  90.         assertEquals("dev", user.getDepartment().getName());   
  91.            
  92.     }   
  93.        
  94.   
  95. }  
/**
 * @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());
		
	}
	

}



我们可以看到,简单的API,就已经能够完成对各种对象树的读取和设值工作了。这也体现出OGNL的学习成本非常低。

在上面的测试用例中,需要特别强调进行区分的,是在针对不同内容进行取值或者设值时,OGNL表达式的不同。

Struts2 Reference 写道
The framework uses a standard naming context to evaluate OGNL expressions. The top level object dealing with OGNL is a Map (usually referred as a context map or context). OGNL has a notion of there being a root (or default) object within the context. In expression, the properties of the root object can be referenced without any special "marker" notion. References to other objects are marked with a pound sign (#).


上面这段内容摘自Struts2的Reference,我把这段话总结为以下2条规则:

A) 针对根对象(Root Object)的操作,表达式是自根对象到被访问对象的某个链式操作的字符串表示。
B) 针对上下文环境(Context)的操作,表达式是自上下文环境(Context)到被访问对象的某个链式操作的字符串表示,但是必须在这个字符串的前面加上#符号,以表示与访问根对象的区别。

上面的这点区别咋看起来非常容易理解,不过一旦放到特定的环境中,就会显示出其重要性,它可以解释很多Struts2在页面展示上取值的各种复杂的表达式的现象。这一点在下一篇文章中会进行具体的分析。

OGNL三要素

我把传入OGNL的API的三个参数,称之为OGNL的三要素。OGNL的操作实际上就是围绕着这三个参数而进行的。

1. 表达式(Expression)

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

我们可以看到,在上面的测试中,name、department.name等都是表达式,表示取name或者department中的name的值。OGNL支持很多类型的表达式,之后我们会看到更多。

2. 根对象(Root Object)

根对象可以理解为OGNL的 操作对象。在表达式规定了“干什么”以后,你还需要指定到底 “对谁干”

在上面的测试代码中,user就是根对象。这就意味着,我们需要对user这个对象去取name这个属性的值(对user这个对象去设置其中的department中的name属性值)。

3. 上下文环境(Context)

有了表达式和根对象,我们实际上已经可以使用OGNL的基本功能。例如,根据表达式对根对象进行取值或者设值工作。

不过实际上,在OGNL的内部,所有的操作都会在一个特定的环境中运行,这个环境就是OGNL的上下文环境(Context)。说得再明白一些,就是这个上下文环境(Context),将规定OGNL的操作 “在哪里干”

OGNL的上下文环境是一个Map结构,称之为OgnlContext。上面我们提到的根对象(Root Object),事实上也会被加入到上下文环境中去,并且这将作为一个特殊的变量进行处理,具体就表现为针对根对象(Root Object)的存取操作的表达式是不需要增加#符号进行区分的。

OgnlContext不仅提供了OGNL的运行环境。在这其中,我们还能设置一些自定义的parameter到Context中,以便我们在进行OGNL操作的时候能够方便的使用这些parameter。不过正如我们上面反复强调的,我们在访问这些parameter时,需要使用#作为前缀才能进行。

OGNL与模板

我们在尝试了OGNL的基本操作并了解了OGNL的三要素之后,或许很容易把OGNL的操作与模板联系起来进行比较。在很多方面,他们也的确有着相似之处。

对于模板,会有一些普通的输出元素,也有一些模板语言特殊的符号构成的元素,这些元素一旦与具体的Java对象融合起来,就会得到我们需要的输出结果。

而OGNL看起来也是非常的类似,OGNL中的表达式就雷同于模板语言的特殊符号,目的是针对某些Java对象进行存取。而OGNL与模板都将数据与展现分开,将数据放到某个特定的地方,具体来说,就是Java对象。只是OGNL与模板的语法结构不完全相同而已。

深入浅出OGNL 

在了解了OGNL的API和基本操作以后,我们来深入到OGNL的内部来看看,挖掘一些更加深入的知识。

OGNL表达式

OGNL支持各种纷繁复杂的表达式。但是最最基本的表达式的原型,是将对象的引用值用点串联起来,从左到右,每一次表达式计算返回的结果成为当前对象,后面部分接着在当前对象上进行计算,一直到全部表达式计算完成,返回最后得到的对象。OGNL则针对这条基本原则进行不断的扩充,从而使之支持对象树、数组、容器的访问,甚至是类似SQL中的投影选择等操作。

接下来我们就来看看一些常用的OGNL表达式:

1. 基本对象树的访问

对象树的访问就是通过使用点号将对象的引用串联起来进行。

例如:name,department.name,user.department.factory.manager.name

2. 对容器变量的访问

对容器变量的访问,通过#符号加上表达式进行。

例如:#name,#department.name,#user.department.factory.manager.name

3. 使用操作符号

OGNL表达式中能使用的操作符基本跟Java里的操作符一样,除了能使用 +, -, *, /, ++, --, ==, !=, = 等操作符之外,还能使用 mod, in, not in等。

4. 容器、数组、对象

OGNL支持对数组和ArrayList等容器的顺序访问:

例如:group.users[0]

同时,OGNL支持对Map的按键值查找:

例如:#session['mySessionPropKey']

不仅如此,OGNL还支持容器的构造的表达式:

例如:{"green", "red", "blue"}构造一个List,#{"key1" : "value1", "key2" : "value2", "key3" : "value3"}构造一个Map

你也可以通过任意类对象的构造函数进行对象新建:

例如:new java.net.URL("http://localhost/")

5. 对静态方法或变量的访问

要引用类的静态方法和字段,他们的表达方式是一样的@class@member或者@class@method(args):

例如:@com.javaeye.core.Resource@ENABLE,@com.javaeye.core.Resource@getAllResources

6. 方法调用

直接通过类似Java的方法调用方式进行,你甚至可以传递参数:

例如:user.getName(),group.users.size(),group.containsUser(#requestUser)

7. 投影和选择


OGNL支持类似数据库中的投影(projection) 和选择(selection)。

投影就是选出集合中每个元素的相同属性组成新的集合,类似于关系数据库的字段操作。投影操作语法为 collection.{XXX},其中XXX 是这个集合中每个元素的公共属性。

例如:group.userList.{username}将获得某个group中的所有user的name的列表。

选择就是过滤满足selection 条件的集合元素,类似于关系数据库的纪录操作。选择操作的语法为:collection.{X YYY},其中X 是一个选择操作符,后面则是选择用的逻辑表达式。而选择操作符有三种:
? 选择满足条件的所有元素
^ 选择满足条件的第一个元素
$ 选择满足条件的最后一个元素

例如:group.userList.{? #this.name != null}将获得某个group中user的name不为空的user的列表。

上述的所有的表达式,只是对OGNL所有表达式的大概的一个概括,除此之外,OGNL还有更多的表达式,例如lamba表达式等等。最具体的表达式的文档,大家可以参考OGNL自带的文档:

http://www.ognl.org/2.6.9/Documentation/html/LanguageGuide/apa.html

在撰写时,我也参考了potain同学的XWork教程以及一些网络上的一些文章,特此列出:

http://www.lifevv.com/java/doc/20071018173750030.html

http://blog.csdn.net/ice_fire2008/archive/2008/05/12/2438817.aspx

OGNLContext

OGNLContext就是OGNL的运行上下文环境。OGNLContext其实是一个Map结构,如果查看一下它的源码,就会发现,它其实实现了java.utils.Map的接口。当你在调用OGNL的取值或者设值的方法时,你可能会自己定义一个Context,并且将它传递给方法。事实上,你所传递进去的这个Context,会在OGNL内部被转化成OGNLContext,而你传递进去的所有的键值对,也会被OGNLContext接管维护,这里有点类似一个装饰器,向你屏蔽了一些其内部的实现机理。

在OGNLContext的内部维护的东西很多,其中,我挑选2个比较重要的提一下。一个是你在调用方法时传入的Context,它会被维护在OGNL内部,并且作为存取变量的基础依据。另外一个,是在Context内部维护了一个key为root的值,它将规定在OGNLContext进行计算时,哪个元素被指定为根对象。其在进行存取时,将会被特殊对待。

this指针

我们知道,OGNL表达式是以点进行串联的一个字符串链式表达式。而这个表达式在进行计算的时候,从左到右,每一次表达式计算返回的结果成为当前对象,并继续进行计算,直到得到计算结果。每次计算的中间对象都会放在一个叫做this的变量里面这个this变量就称之为this指针。

例如:group.userList.size().(#this+1).toString()

在这个例子中,#this其实就是group.userList.size()的计算结构。

使用this指针,我们就可以在OGNL表达式中进行一些简单的计算,从而完成我们的计算逻辑,而this指针在lamba表达式的引用中尤为广泛,有兴趣的读者可以深入研究OGNL自带的文档中lamba表达式的章节。

默认行为和类型转化

在我们所讲述的所有的OGNL的操作中,实际上,全部都忽略了OGNL内部帮助你完成的很多默认行为和类型转化方面的工作。

我们来看一下OGNL在进行操作初始化时候的一个函数签名:

Java代码
  1. /**  
  2.  * Appends the standard naming context for evaluating an OGNL expression  
  3.  * into the context given so that cached maps can be used as a context.  
  4.  *  
  5.  * @param root the root of the object graph  
  6.  * @param context the context to which OGNL context will be added.  
  7.  * @return Context Map with the keys <CODE>root</CODE> and <CODE>context</CODE>  
  8.  *         set appropriately  
  9.  */  
  10. public static Map addDefaultContext( Object root, ClassResolver classResolver, TypeConverter converter, MemberAccess memberAccess, Map context );  
/**
 * Appends the standard naming context for evaluating an OGNL expression
 * into the context given so that cached maps can be used as a context.
 *
 * @param root the root of the object graph
 * @param context the context to which OGNL context will be added.
 * @return Context Map with the keys <CODE>root</CODE> and <CODE>context</CODE>
 *         set appropriately
 */
public static Map addDefaultContext( Object root, ClassResolver classResolver, TypeConverter converter, MemberAccess memberAccess, Map context );



可以看到,在初始化时,OGNL还需要额外初始化一个类型转化的接口和一些其他的信息。只不过这些默认行为,由OGNL的内部屏蔽了。

一旦需要自己定义针对某个特定类型的类型转化方式,你就需要实现TypeConverter接口,并且在OGNL中进行注册。

同时,如果需要对OGNL的许多默认行为做出改变,则需要通过设置OGNL的全局环境变量进行。

上述的这些内容,有些会在后面的章节涉及,有兴趣的读者,也可以参阅OGNL的源码和OGNL的文档寻求帮助。
在Struts2中使用OGNL:
OGNL是XWork引入的一个非常有效的数据处理的工具。我们已经了解了OGNL的基本操作和OGNL的内部结构,接下来,我们来看看XWork对OGNL做了什么样的加强,以及OGNL的体系在Struts2中如何运转。

从例子开始 

我们先从一个例子开始,看看数据在Struts2中是如何运转的。

Java代码
  1. /**  
  2.  * @author Downpour  
  3.  */  
  4. public class User {   
  5.        
  6.     private Integer id;   
  7.        
  8.     private String name;   
  9.        
  10.     private Department department = new Department();   
  11.        
  12.         // setter and getters   
  13. }   
  14.   
  15. //=========================================================================   
  16.   
  17. /**  
  18.  * @author Downpour  
  19.  */  
  20. public class Department {   
  21.        
  22.     private Integer id;   
  23.        
  24.     private String name;   
  25.            
  26.         // setter and getters   
  27. }   
  28.   
  29. //=========================================================================   
  30.   
  31. <form method="post" action="/struts-example/ognl.action">   
  32.     user name: <input type="text" name="user.name" value="downpour" />   
  33.     department name: <input type="text" name="department.name" value="dev" />   
  34.     <input type="submit" value="submit" />   
  35. </form>   
  36.   
  37. //=========================================================================   
  38.   
  39. /**  
  40.  * @author Downpour  
  41.  */  
  42. public class OgnlAction extends ActionSupport {   
  43.   
  44.     private static final Log logger = LogFactory.getLog(OgnlAction.class);   
  45.   
  46.     private User user;   
  47.        
  48.     private Department department;   
  49.        
  50.     /* (non-Javadoc)  
  51.      * @see com.opensymphony.xwork2.ActionSupport#execute()  
  52.      */  
  53.     @Override  
  54.     public String execute() throws Exception {   
  55.         logger.info("user name:" + user.getName());   // -> downpour   
  56.         logger.info("department name:" + department.getName());   // -> dev   
  57.         return super.execute();   
  58.     }   
  59.   
  60.     // setter and getters   
  61. }   
  62.   
  63. //=========================================================================   
  64.   
  65. user name: <s:property value="user.name" />   
  66. department name: <s:property value="department.name" />   
  67.   
  68. //=========================================================================  
/**
 * @author Downpour
 */
public class User {
	
	private Integer id;
	
	private String name;
	
	private Department department = new Department();
	
        // setter and getters
}

//=========================================================================

/**
 * @author Downpour
 */
public class Department {
	
	private Integer id;
	
	private String name;
		
        // setter and getters
}

//=========================================================================

<form method="post" action="/struts-example/ognl.action">
    user name: <input type="text" name="user.name" value="downpour" />
    department name: <input type="text" name="department.name" value="dev" />
    <input type="submit" value="submit" />
</form>

//=========================================================================

/**
 * @author Downpour
 */
public class OgnlAction extends ActionSupport {

    private static final Log logger = LogFactory.getLog(OgnlAction.class);

    private User user;
    
    private Department department;
    
    /* (non-Javadoc)
     * @see com.opensymphony.xwork2.ActionSupport#execute()
     */
    @Override
    public String execute() throws Exception {
        logger.info("user name:" + user.getName());   // -> downpour
        logger.info("department name:" + department.getName());   // -> dev
        return super.execute();
    }

    // setter and getters
}

//=========================================================================

user name: <s:property value="user.name" />
department name: <s:property value="department.name" />

//=========================================================================


我们可以看到在JSP中,form中的元素input等,都使用OGNL的表达式作为name的值。而在form提交时,这些值都会被设置到Action中的Java对象中。而当Action转向到JSP时,Struts2的Tag又可以从Action的Java对象中,通过OGNL进行取值。

在这里,你看不到任何的OGNL的代码级别操作,因为这些都在Struts2内部进行了封装。而这些封装,都是建立在OGNL的基本概念,也就是根对象和上下文环境之上。下面就分别就这两个方面分别进行讲解。

ValueStack —— 对OGNL的加强 

细心的读者可能会发现,在上面的例子中,我们使用了不同的表达式,针对Action中的不同的Java对象进行设值。再结合上一讲我们所例举的OGNL的代码操作示例,我们有强烈的理由怀疑,Struts2在内部有可能执行了这样的操作,才使得页面到Action的设值工作顺利完成:

Java代码
  1. // "user.name" as OGNL expression, action as OGNL Root object   
  2. Ognl.setValue("user.name", action, "downpour");   
  3. Ognl.setValue("department.name", action, "dev");  
// "user.name" as OGNL expression, action as OGNL Root object
Ognl.setValue("user.name", action, "downpour");
Ognl.setValue("department.name", action, "dev");


如果这个怀疑是正确的,那么我们就能得出这样一个结论: Struts2的Action是OGNL操作的根对象。

这个结论是我们从现象上推出来的,至于它到底正确与否,我们之后可以通过源码分析来进行验证,在这里先卖一个关子,姑且认为它是正确的。不过这个结论对我们来说非常重要,因为这个结论Struts2的Tag,JSTL和Freemarker等表示层元素获取Action中变量的值打下了坚实的基础。

在Struts2(XWork)中,不仅把Action作为OGNL操作的根对象,作为对OGNL的扩展,它还引入了一个 ValueStack的概念。这个概念代表了什么呢?还是让我们看看Struts2的Reference怎么说:
Struts2 Reference 写道
The biggest addition that XWork provides on top of OGNL is the support for the ValueStack. While OGNL operates under the assumption there is only one "root", XWork's ValueStack concept requires there be many "roots".

很明显,ValueStack依照它的结构和作用,至少为我们提供两大特性:

1. ValueStack是一个堆栈结构,堆栈中的每个元素对于OGNL操作来说,都被看作是根对象。

2. 由于ValueStack是一个堆栈结构,所以其中的元素都是有序的,对于某个OGNL表达式来说,OGNL将自堆栈顶部开始查找,并返回第一个符合条件的对象元素。


这里我们有必要对第二点啰嗦几句,举个具体的例子来说(这个例子同样摘自Struts2的Reference):如果在ValueStack中有2个对象,分别是“动物”和“人”,这两个对象都具备一个属性,叫做name,而“动物”还有一个属性叫species,“人”还有个属性叫salary。其中,“动物”对象在ValueStack的栈顶,而“人”这个对象在栈底。那么看看下面的OGNL表达式将返回什么?

Java代码
  1. species    // call to animal.getSpecies()   
  2. salary     // call to person.getSalary()   
  3. name       // call to animal.getName() because animal is on the top  
species    // call to animal.getSpecies()
salary     // call to person.getSalary()
name       // call to animal.getName() because animal is on the top


对于name这个属性,返回的将是“动物”的name,因为“动物”在栈顶,会被先匹配到OGNL的表达式。但是有的时候,你可能需要访问“人”的name属性,怎么办呢?你可以通过下面的方法:

Java代码
  1. [0].name   // call to animal.getName()   
  2. [1].name   // call to person.getName()  
[0].name   // call to animal.getName()
[1].name   // call to person.getName()

Struts2中的OGNL上下文环境 

有了ValueStack,我们再来仔细研究一下Struts2中OGNL的上下文环境。

Struts2 Reference 写道
The framework sets the OGNL context to be our ActionContext, and the value stack to be the OGNL root object. (The value stack is a set of several objects, but to OGNL it appears to be a single object.) Along with the value stack, the framework places other objects in the ActionContext, including Maps representing the application, session, and request contexts. These objects coexist in the ActionContext, alongside the value stack (our OGNL root)


也就是说,ActionContext是Struts2中OGNL的上下文环境。它维护着一个Map的结构,下面是这个结构的图示:



其中,ValueStack是这个上下文环境中的根对象,而除了这个根对象以外,Struts2还在这个上下文环境中放了许多额外的变量,而这些变量多数都是被XWork封装过的Servlet对象,例如request,session,servletContext(application)等,这些对象都被封装成Map对象,随着ActionContext作用于整个Action执行的生命周期中。

在这里,或许有些读者会提出问题来,为什么好好的Servlet对象要在这里被封装成Map对象呢?我想原因可能有以下两个:

1. 对Struts2的Action彻底屏蔽Servlet容器,从而无需再使用底层Servlet API进行编程。你所面对的,将永远是一个又一个的Java对象。

2. 便于各种View技术,例如JSP,Freemarker,Velocity等对ValueStack中上下文环境,尤其是Servlet对象中的数据进行读取。试想,如果在这里不将HttpServletRequest,HttpSession等Servlet对象转化成Map,那么我们将很难通过OGNL表达式,对这些Servlet对象中的值进行读取。

Struts2中使用OGNL进行计算 

取值计算

有了上面的这些知识,我们就能非常容易的理解在Struts2中如何使用OGNL进行取值计算。

提问:在Struts2中,如何使用自身的Tag读取Action中的变量?

Struts2自身的Tag会根据value中的OGNL表达式,在ValueStack中寻找相应的对象。因为action在ValueStack的顶部,所以默认情况下,Struts2的Tag中的OGNL表达式将查找action中的变量。请注意,value中的内容直接是OGNL表达式,无需任何el的标签包装。

例如:<s:property value="user.name" />

提问:在Struts2中,如何使用自身的Tag读取HttpServletRequest,HttpSession中的变量?

在上面的知识中,我们知道,Struts2中OGNL的上下文环境中,包含request,session,application等servlet对象的Map封装。既然这些对象都在OGNL的上下文中,那么根据OGNL的基本知识,我们可以通过在表达式前面加上#符号来对这些变量的值进行访问。

例如:<s:property value="%{#application.myApplicationAttribute}" />
<s:property value="%{#session.mySessionAttribute}" />
<s:property value="%{#request.myRequestAttribute}" />
<s:property value="%{#parameters.myParameter}" />

在这里啰嗦一句,在Tag的value中包括%{开头和}结尾的字符串,不知道Struts2为什么要做出这样的设置,从源码上看,它似乎没有什么特别额外的作用:

Java代码
  1. if (value == null) {   
  2.             value = "top";   
  3.         }   
  4.         else if (altSyntax()) {   
  5.             // the same logic as with findValue(String)   
  6.             // if value start with %{ and end with }, just cut it off!   
  7.             if (value.startsWith("%{") && value.endsWith("}")) {   
  8.                 value = value.substring(2, value.length() - 1);   
  9.             }   
  10.         }   
  11.   
  12.         // exception: don't call findString(), since we don't want the   
  13.         //            expression parsed in this one case. it really   
  14.         //            doesn't make sense, in fact.   
  15.         actualValue = (String) getStack().findValue(value, String.class);   
  16.            
  17.         ......   
  18.   
  19. }  
if (value == null) {
            value = "top";
        }
        else if (altSyntax()) {
            // the same logic as with findValue(String)
            // if value start with %{ and end with }, just cut it off!
            if (value.startsWith("%{") && value.endsWith("}")) {
                value = value.substring(2, value.length() - 1);
            }
        }

        // exception: don't call findString(), since we don't want the
        //            expression parsed in this one case. it really
        //            doesn't make sense, in fact.
        actualValue = (String) getStack().findValue(value, String.class);
        
        ......

}


有兴趣的朋友可以研究一下,这一对符号的原理究竟是什么。

提问:在Struts2中,如何使用JSTL来读取Action中的变量?

这是一个历史悠久的问题。因为事实上,很多朋友(包括我在内)是不使用Struts2自身的标签库,而是使用JSTL的,可能因为JSTL标签库比较少,简单易用的原因吧。

我们知道,JSTL默认是从page,request,session,application这四个Scope逐次查找相应的EL表达式所对应的对象的值。那么如果要使用JSTL来读取Action中的变量,就需要把Action中的变量,放到request域中才行。所以,早在Webwork2.1.X的年代,我们会编写一个拦截器来做这个事情的。大致的原理是:在Action执行完返回之前,依次读取Action中的所有的变量,并依次调用request.setAttribute()来进行设置。具体的整合方式,请参考以下这篇文档: http://wiki.opensymphony.com/display/WW/Using+WebWork+and+XWork+with+JSP+2.0+and+JSTL+1.1

不过随着时代的发展,上面的这种方式,已经不再被推荐使用了。(虽然如此,我们依然可以学习它的一个解决问题的思路)目前来说,自从Webwork2.2以后,包括Struts2,都使用另外一种整合方式:对HttpServletRequest进行装饰。让我们来看一下源码:

Java代码
  1. public class StrutsRequestWrapper extends HttpServletRequestWrapper {   
  2.   
  3.     /**  
  4.      * The constructor  
  5.      * @param req The request  
  6.      */  
  7.     public StrutsRequestWrapper(HttpServletRequest req) {   
  8.         super(req);   
  9.     }   
  10.   
  11.     /**  
  12.      * Gets the object, looking in the value stack if not found  
  13.      *  
  14.      * @param s The attribute key  
  15.      */  
  16.     public Object getAttribute(String s) {   
  17.         if (s != null && s.startsWith("javax.servlet")) {   
  18.             // don't bother with the standard javax.servlet attributes, we can short-circuit this   
  19.             // see WW-953 and the forums post linked in that issue for more info   
  20.             return super.getAttribute(s);   
  21.         }   
  22.   
  23.         ActionContext ctx = ActionContext.getContext();   
  24.         Object attribute = super.getAttribute(s);   
  25.   
  26.         boolean alreadyIn = false;   
  27.         Boolean b = (Boolean) ctx.get("__requestWrapper.getAttribute");   
  28.         if (b != null) {   
  29.             alreadyIn = b.booleanValue();   
  30.         }   
  31.   
  32.         // note: we don't let # come through or else a request for   
  33.         // #attr.foo or #request.foo could cause an endless loop   
  34.         if (!alreadyIn && attribute == null && s.indexOf("#") == -1) {   
  35.             try {   
  36.                 // If not found, then try the ValueStack   
  37.                 ctx.put("__requestWrapper.getAttribute", Boolean.TRUE);   
  38.                 ValueStack stack = ctx.getValueStack();   
  39.                 if (stack != null) {   
  40.                     attribute = stack.findValue(s);   
  41.                 }   
  42.             } finally {   
  43.                 ctx.put("__requestWrapper.getAttribute", Boolean.FALSE);   
  44.             }   
  45.         }   
  46.         return attribute;   
  47.     }   
  48. }  
public class StrutsRequestWrapper extends HttpServletRequestWrapper {

    /**
     * The constructor
     * @param req The request
     */
    public StrutsRequestWrapper(HttpServletRequest req) {
        super(req);
    }

    /**
     * Gets the object, looking in the value stack if not found
     *
     * @param s The attribute key
     */
    public Object getAttribute(String s) {
        if (s != null && s.startsWith("javax.servlet")) {
            // don't bother with the standard javax.servlet attributes, we can short-circuit this
            // see WW-953 and the forums post linked in that issue for more info
            return super.getAttribute(s);
        }

        ActionContext ctx = ActionContext.getContext();
        Object attribute = super.getAttribute(s);

        boolean alreadyIn = false;
        Boolean b = (Boolean) ctx.get("__requestWrapper.getAttribute");
        if (b != null) {
            alreadyIn = b.booleanValue();
        }

        // note: we don't let # come through or else a request for
        // #attr.foo or #request.foo could cause an endless loop
        if (!alreadyIn && attribute == null && s.indexOf("#") == -1) {
            try {
                // If not found, then try the ValueStack
                ctx.put("__requestWrapper.getAttribute", Boolean.TRUE);
                ValueStack stack = ctx.getValueStack();
                if (stack != null) {
                    attribute = stack.findValue(s);
                }
            } finally {
                ctx.put("__requestWrapper.getAttribute", Boolean.FALSE);
            }
        }
        return attribute;
    }
}


看到了嘛?这个类会在Struts2初始化的时候,替换HttpServletRequest,运行于整个Struts2的运行过程中,当我们试图调用request.getAttribute()的时候,就会执行上面的这个方法。(这是一个典型的装饰器模式)在执行上面的方法时,会首先调用HttpServletRequest中原本的request.getAttribute(),如果没有找到,它会继续到ValueStack中去查找,而action在ValueStack中,所以action中的变量通过OGNL表达式,就能找到对应的值了。

在这里,在el表达式广泛使用的今天,JSTL1.1以后,也支持直接使用el表达式。注意与直接使用struts2的tag的区别,这里需要使用el的表示符号:${}

例如:${user.name}, <c:out value="${department.name}" />

提问:在Struts2中,如何使用Freemarker等模板来读取Action中的变量以及HttpServletRequest和HttpSession中的变量?

Freemarker等模板在Struts2中有对应的Result,而在这些Result中,Freemarker等模板会根据ValueStack和ActionContext中的内容,构造这些模板可识别的Model,从而使得模板可以以他们各自的语法对ValueStack和ActionContext中的内容进行读取。

有关Freemarker对于变量的读取,可以参考Struts2的官方文档,非常详细: http://struts.apache.org/2.0.14/docs/freemarker.html

设值计算

Struts2中使用OGNL进行设值计算,就是指View层传递数据到Control层,并且能够设置到相应的Java对象中。这个过程从逻辑上说需要分成两步来完成:

1. 对于每个请求,都建立一个与相应Action对应的ActionContext作为OGNL的上下文环境和ValueStack,并且把Action压入ValueStack

2. 在请求进入Action代码前,通过某种通用的机制,搜集页面上传递过来的参数,并调用OGNL相关的代码,对Action进行设值。

上面的第一个步骤,在处理URL请求时完成,而第二个步骤,则涉及到另外一个XWork的核心知识:拦截器。所以有关Struts2使用OGNL进行设值计算的详细分析,将会在拦截器章节具体给出。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值