struts2.0与ognl浅析

本文详细介绍了Struts2框架如何利用OGNL(Object Graph Navigation Language)解决数据流转中的不匹配性问题。文章探讨了OGNL的基本操作、三要素、上下文环境等内容,并深入解析了Struts2中ValueStack的概念及其在OGNL操作中的角色。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

struts2.0与ognl浅析(转载)

(2010-02-24 11:34:17)
标签:

struts2.0

ognl

杂谈

分类: java

OGNL —— 完美的催化剂

         为了解决数据从View层传递到Controller层时的不匹配性,Struts2采纳了XWork的OGNL方案。并且在OGNL的基础上,构建了OGNLValueStack的机制 ,从而比较完美的解决了数据流转中的不匹配性。

         OGNL(Object Graph Navigation Language),是一种表达式语言。使用这种表达式语言,你可以通过某种表达式语法,存取Java对象树中的任意属性 、调用Java对象树的方法、同时能够自动实现必要的类型转化 如果我们把表达式看做是一个带有语义的字符串,那么OGNL无疑成为了这个语义字符串与Java对象之间沟通的桥梁。

如何使用OGNL

让我们先研究一下OGNL的API,他来自于Ognl的静态方法:

Java代码 复制代码
  1.   
  2.   
  3. public   static  Object getValueObject tree, Map context, Object root  )  throws  OgnlException;   
  4.   
  5.   
  6. public   static   void  setValueObject tree, Map context, Object root, Object value  )  throws  OgnlException  

 

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

 

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

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

Struts2 Reference 写道
The framework uses a standard naming context to evaluate OGNL expression_r_rs. 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_r_r, 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_r_r)

      表达式是整个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自带的文档:


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在进行操作初始化时候的一个函数签名:

 

  1.   
  2. public   static  Map addDefaultContext( Object root, ClassResolver classResolver, TypeConverter converter, MemberAccess memberAccess, Map context );  

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

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

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

OGNL是XWork引入的一个非常有效的数据处理的工具。我们已经了解了OGNL的基本操作和OGNL的内部结构,接下来,我们来看看XWork对OGNL做了什么样的加强,以及OGNL的体系在Struts2中如何运转。

  1.   
  2. public   class  User {   
  3.        
  4.      private  Integer id;   
  5.        
  6.      private  String name;   
  7.        
  8.      private  Department department =  new  Department();   
  9.        
  10.          // setter and getters   
  11. }   
  12.   
  13. //=========================================================================   
  14.   
  15.   
  16. public   class  Department {   
  17.        
  18.      private  Integer id;   
  19.        
  20.      private  String name;   
  21.            
  22.          // setter and getters   
  23. }   
  24.   
  25. //=========================================================================   
  26.   
  27. <form method= "post"  action= "/struts-example/ognl.action" >   
  28.     user name: <input type= "text"  name= "user.name"  value= "downpour"  />   
  29.     department name: <input type= "text"  name= "department.name"  value= "dev"  />   
  30.     <input type= "submit"  value= "submit"  />   
  31. </form>   
  32.   
  33. //=========================================================================   
  34.   
  35.   
  36. public   class  OgnlAction  extends  ActionSupport {   
  37.   
  38.      private   static   final  Log logger = LogFactory.getLog(OgnlAction. class );   
  39.   
  40.      private  User user;   
  41.        
  42.      private  Department department;   
  43.        
  44.        
  45.      @Override   
  46.      public  String execute()  throws  Exception {   
  47.         logger.info( "user name:"  + user.getName());    // -> downpour   
  48.         logger.info( "department name:"  + department.getName());    // -> dev   
  49.          return   super .execute();   
  50.     }   
  51.   
  52.      // setter and getters   
  53. }   
  54.   
  55. //=========================================================================   
  56.   
  57. user name: <s:property value= "user.name"  />   
  58. department name: <s:property value= "department.name"  />   
  59.   
  60. //=========================================================================   
我们可以看到在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的设值工作顺利完成:
  1. // "user.name" as OGNL expression_r_r, action as OGNL Root object   
  2. Ognl.setValue( "user.name" , action,  "downpour" );   
  3. 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表达式将返回什么?

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

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

  1. [ 0 ].name    // call to animal.getName()   
  2. [ 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对象中的值进行读取。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值