强大的OGNL

第6章 灵丹妙药 —— OGNL,数据流转的催化剂 

6.2 强大的OGNL 

OGNL (Object Graph Navigation Language)  是一个开源的表达式引擎。通过使用OGNL,我们能够通过表达式存取Java对象树中的任意属性和调用Java对象树的方法等。也就是说,如果我们把表达式看成是一个带有语义的字符串,那么OGNL就是这个语义字符串与Java对象之间沟通的催化剂,通过OGNL,我们可以轻松解决在数据流转的过程中所碰到的各种问题。 

6.2.1深入OGNL的API 

我们首先用最直观的方式:通过研究OGNL的原生API来看看如何使用OGNL来进行对象的存取操作。首先来看一下来自于OGNL的静态方法,如代码清单6-4所示: 

Java代码  

  1. /**  
  2.  * 通过传入的OGNL表达式,在给定的上下文环境中,从root对象里取值 
  3.  */  
  4. public static Object getValue(String expression, Map context, Object root) throws OgnlException {  
  5.   return getValue(expression, context, root, null);  
  6. }  
  7.   
  8. /** 
  9.  * 通过传入的OGNL表达式,在给定的上下文环境中,往root对象里写值 
  10.  */  
  11. public static void setValue(String expression, Map context, Object root, Object value) throws OgnlException {  
  12.   setValue(parseExpression(expression), context, root, value);  
  13. }  

OGNL的API其实相当简单,上面的2个方法,分别针对对象的“取值”和“写值”操作。因而,OGNL的基本操作实际上是通过传入上述这2个方法的三个参数来实现的。OGNL同时编写了许多其他的方法来实现相同的功能,上述的2个接口只是其中最简单并最具代表性的2个方法。读者可以通过阅读Ognl.java来获取更多的信息。 

在初步浏览了OGNL的API后,我们可以编写一个单元测试来测试一下上面列出来的OGNL静态方法接口,实现如代码清单6-5所示。 

Java代码  

  1. public class BasicOgnlTest extends TestCase {  
  2.       
  3.      @SuppressWarnings("unchecked")  
  4.      @Test  
  5.      public void testGetValue() throws Exception {   
  6.     // 创建Root对象  
  7.     User user = new User();    
  8.     user.setId(1);    
  9.     user.setName("downpour");    
  10.           
  11.     // 创建上下文环境  
  12.     Map context = new HashMap();    
  13.     context.put("introduction","My name is ");    
  14.             
  15.     // 测试从Root对象中进行表达式计算并获取结果  
  16.     Object name = Ognl.getValue(Ognl.parseExpression("name"), user);    
  17.     assertEquals("downpour",name);    
  18.             
  19.     // 测试从上下文环境中进行表达式计算并获取结果  
  20.     Object contextValue = Ognl.getValue(Ognl.parseExpression("#introduction"), context, user);    
  21.     assertEquals("My name is ", contextValue);    
  22.             
  23.     // 测试同时从将Root对象和上下文环境作为表达式的一部分进行计算   
  24.     Object hello = Ognl.getValue(Ognl.parseExpression("#introduction + name"), context, user);    
  25.     assertEquals("My name is downpour", hello);    
  26.      }  
  27.       
  28.      @Test  
  29.      public void testSetValue() throws Exception {  
  30.     // 创建Root对象  
  31.     User user = new User();    
  32.     user.setId(1);    
  33.     user.setName("downpour");    
  34.         
  35.     // 对Root对象进行写值操作   
  36.     Ognl.setValue("group.name", user, "dev");    
  37.     Ognl.setValue("age", user, "18");  
  38.           
  39.     assertEquals("dev", user.getGroup().getName());  
  40.      }  
  41. }  

我们可以看到,通过简单的API就能够完成对各种对象树的“取值”和“写值”操作。而“取值”和“写值”工作是我们日后所有工作的基础,如果我们要深入了解OGNL的细节,就需要对传入OGNL的这3个参数进行研究。这3个参数,被我们称之为OGNL的三要素。在下一节中,我们会对OGNL的三要素做具体的解释。 

OGNL的API是极其简单的,无论是何种复杂的功能,OGNL最终会将其最终映射到OGNL的三要素中,通过调用底层引擎完成计算。OGNL对于其构成要素的设计思路,完全契合了我们对表达式引擎的要求,因而也成为了众多表达式引擎设计的一种标准。如果我们翻开其它的一些著名的表达式引擎,同样可以看到这些构成要素的身影。 

以Spring框架所发布的内置表达式引擎SpringEL为例,我们可以在其核心的Expression操作接口中看到完全相同的构成要素定义。如图6-2所示: 

读者在这里应该仔细品味表达式引擎自身的特性和构成要素之间的联系和共同点,领略其中的设计精髓并熟练运用到实际开发中去。 

6.2.2 OGNL三要素 

从上一节的例子中我们可以看到,每进行一次OGNL操作都需要3个参数。OGNL的所有操作实际上都是围绕着这3个参数而进行的。这3个参数被称之为OGNL的三要素。 

6.2.2.1表达式(Expression) 

表达式是整个OGNL的核心,所有的OGNL操作都是针对表达式的解析后进行的。表达式会规定此次OGNL操作到底要干什么。因此,表达式其实是一个带有语法含义的字符串,这个字符串将规定操作的类型和操作的内容。 

OGNL支持大量的表达式语法,不仅支持“链式”描述对象访问路径,还支持在表达式中进行简单的计算,甚至还能够支持复杂的Lambda表达式等。我们可以在接下来的章节中看到各种各样不同的OGNL表达式。 

6.2.2.2 Root对象(Root Object) 

OGNL的Root对象可以理解为OGNL的操作对象。当OGNL表达式规定了“干什么”以后,我们还需要指定对谁干。OGNL的Root对象实际上是一个Java对象,是所有OGNL操作的实际载体。这就意味着,如果我们有一个OGNL的表达式,那么我们实际上需要针对Root对象去进行OGNL表达式的计算并返回结果。 

6.2.2.3上下文环境(Context) 

有了表达式和Root对象,我们已经可以使用OGNL的基本功能。例如,根据表达式针对OGNL中的Root对象进行“取值”或者“写值”操作。 

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

OGNL的上下文环境是一个Map结构,称之为OgnlContext。之前我们所提到的Root对象(Root Object),事实上也会被添加到上下文环境中去,并且将被作为一个特殊的变量进行处理。 

6.2.3 OGNL的基本操作 

6.2.3.1 对Root对象(Root Object)的访问 

针对OGNL的Root对象的对象树的访问是通过使用“点号”将对象的引用串联起来实现的。通过这种方式,OGNL实际上将一个树形的对象结构转化成了一个链式结构的字符串结构来表达语义。 

Java代码  

  1. // 获取Root对象中的name属性的值  
  2. name       
  3. // 获取Root对象中department属性中的name属性的实际值  
  4. department.name     
  5. // 获取Root对象中department属性中manager属性中name属性的实际值  
  6. department.manager.name   

  

6.2.3.2 对上下文环境(Context)的访问 

由于OGNL的上下文是一个Map结构,在OGNL进行计算时可以事先在上下文环境中设置一些参数,并让OGNL将这些参数带入进行计算。有时候也需要对这些上下文环境中的参数进行访问,访问这些参数时,需要通过#符号加上链式表达式来进行,从而表示与访问Root对象(Root Object)的区别。 

Java代码  

  1. // 获取OGNL上下文环境中名为introduction的对象的值  
  2. #introduction      
  3. // 获取OGNL上下文环境中名为parameters的对象中user对象中名为name的属性的值  
  4. #parameters.user.name  

6.2.3.3 对静态变量的访问 

在OGNL中,对于静态变量或者静态方法的访问,需要通过@[class]@[field / method]的表达式语法来进行。 

Java代码  

  1. // 访问com.example.core.Resource 类中名为ENABLE的属性值  
  2. @com.example.core.Resource@ENABLE      
  3. // 调用com.example.core.Resource 类中名为get的方法@com.example.core.Resource@get()  

6.2.3.4方法调用 

在OGNL中调用方法,可以直接通过类似Java的方法调用方式进行,也就是通过点号加方法名称完成方法调用,甚至可以传递参数。 

Java代码  

  1. // 调用Root对象中的group属性中users的size()方法  
  2. group.users.size()      
  3. // 调用Root对象中的group中的containsUser的方法,并将上下文环境中名为requestUser的值作为参数传入  
  4. group.containsUser(#requestUser)  

6.2.3.5使用操作符进行简单计算 

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

Java代码  

  1. 2+4 // 加   
  2. ‘hello’ + ’‘world‘’ // 字符串叠加  
  3. 5-3 // 减  
  4. 9/2 // 除  
  5. 9 mod 2 // 取模  
  6. foo++ // 递增  
  7. foo == bar // 等于判断  
  8. foo in list // 是否在容器中  

6.2.3.6 对数组和容器的访问 

OGNL表达式可以支持对数组按照数组下标的顺序进行访问。同样的方法可以用于有序的容器,如ArrayList,LinkedHashSet等。对于Map结构,OGNL支持根据键值进行访问。 

Java代码  

  1. // 访问Root对象中的group属性中users中第一个对象的name属性值  
  2. group.users[0].name     
  3. // 访问OGNL上下文中名为sessionMap的Map对象中key为currentLogonUser的值  
  4. #sessionMap['currentLogonUser']  

6.2.3.7投影与选择 

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

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

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

  • ? 选择满足条件的所有元素
  • ^ 选择满足条件的第一个元素
  • $ 选择满足条件的最后一个元素

 

Java代码  

  1. // 返回Root对象中的group属性中users这个集合中所有元素的name构成的集合  
  2. group.users.{name}   // 新的以name为元素的集合  
  3. // 将group中users这个集合中的元素的code和name用-连接符拼起来构成的字符串集合  
  4. group.users.{code + ‘-’ + name}   // 新的以‘code – name’为元素的集合  
  5. // 返回Root对象的group中users这个集合所有元素中name不为null的元素构成的集合  
  6. group.users.{? #this.name != null}  // 过滤后的users集合  

6.2.3.8构造对象 

OGNL支持直接通过表达式来构造对象。构造的方式主要包括3种: 

  • 构造List —— 使用{},中间使用逗号隔开元素的方式表达列表
  • 构造Map —— 使用#{},中间使用逗号隔开键值对,并使用冒号隔开key和value来构造Map
  • 构造对象 —— 直接使用已知对象的构造函数来构造对象

 

Java代码  

  1. // 构造一个List  
  2. {"green", "red", "blue"}  
  3. // 构造一个Map  
  4. #{"key1" : "value1", "key2" : "value2", "key3" : "value3"}  
  5. // 构造一个java.net.URL对象  
  6. new java.net.URL("http://localhost/")  

构造对象对于一个表达式语言来说是一个非常强大的功能,OGNL不仅能够直接对容器对象构造提供语法层面的支持,还能够对任意的Java对象提供支持。这样一来就使得OGNL不仅仅具备了数据运算这一简单的功能,同时还被赋予了潜在的逻辑计算功能。 

【OGNL带来的潜在问题】 

我们已经能够看到OGNL在语法层面所表现出来的强大之处。然而,越强大的东西,其自身也一定存在着致命的弱点,这也就是所谓的“物极必反”。正是由于OGNL能够支持完整的Java对象创建、读写过程,它就能被作为一个潜在的切入点,成为黑客的攻击目标。 

exploit-db网站在2010年的7月14日就爆出了一个Struts2的远程执行任意代码的漏洞。具体的声明链接为:http://www.exploit-db.com/exploits/14360/。 

细心的读者会发现,这个漏洞的基本原理实际上就是利用了OGNL可以任意构造对象,并执行对象中方法的特性,构造了一个底层命令调用的Java类,并执行操作系统命令进行系统攻击。 

在Struts2.2.X之后的版本中,这个漏洞被修复,其主要的方法也是通过限制参数名称的方式,拒绝类似的代码执行方式。 

6.2.4深入this指针 

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

【各种编程语言的this指针】 

this指针是许多编程语言都具备的特殊关键字。绝大多数语言中的this指针的含义都是类似的,表示“当前所在函数的调用者”。无论一个表达式有多么复杂,只要读者能够仔细分析this指针所在的函数,并找到这个函数的调用者,就能很容易找到this指针所指向的内容了。 

在OGNL的表达式中的this指针,无疑指向了当前计算的“调用者”对应的实例。如果读者从“调用者”这个角度来理解this指针,那么这个概念就能够被消化和理解了。需要注意的是,如果试图在表达式中使用this指针,需要在this之前加上#,我们来看下面的例子。 

Java代码  

  1. // 返回group中users这个集合中所有age比3大的元素构成的集合  
  2. users.{? #this.age > 3}     
  3. // 返回group中users这个集合里的大小+1的值  
  4. group.users.size().(#this+1)  
  5. // 返回Root对象的group中users这个集合所有元素中name不为null的元素构成的集合group.users.{? #this.name != null}   

this指针在lambda表达式中运用极为广泛,通过this指针,我们能够写出许多简单而又蕴含着复杂逻辑的OGNL表达式,大家可以在实践中慢慢领悟其中的奥妙。 

6.2.5有关#符号的三种用途 

在之前的表达式范例中,我们已经了解了“#”操作符的几种不同用途。这是一个非常容易混淆的知识点,所以非常有必要在这里详细解释一下。 

  • 加在普通OGNL表达式前面,用于访问OGNL上下文中的变量
  • 使用#{}语法动态构建Map
  • 加在this指针之前表示对this指针的引用

这3种不同的用途在不同的地方有着不同的妙用,尤其是对OGNL上下文中的变量的访问,将成为Struts2在页面级别进行容器变量访问的重要理论基础。 

原文链接:[http://wely.iteye.com/blog/2295286]

时间: 2024-09-07 12:20:35

强大的OGNL的相关文章

mybaits入门(含实例教程和源码)

版权声明:本文为博主原创文章,转载注明出处http://blog.csdn.net/u013142781 目录(?)[+] 前言:mybatis是一个非常优秀的存储过程和高级映射的优秀持久层框架.大大简化了,数据库操作中的常用操作.下面将介绍mybatis的一些概念和在eclipse上的实际项目搭建使用. 一.mybatis的概念介绍 1.1.背景介绍 MyBatis 是支持普通 SQL查询,存储过程和高级映射的优秀持久层框架.MyBatis 消除了几乎所有的JDBC代码和参数的手工设置以及结果

OGNL语言介绍与实践

OGNL 的历史 OGNL 最初是为了能够使用对象的属性名来建立 UI 组件 (component) 和 控制器 (controllers) 之间的联系,简单来说就是:视图 与 控制器 之间数据的联系.后来为了应付更加复杂的数据关系,Drew Davidson 发明了一个被他称为 KVCL(Key-Value Coding Language) 的语言.Luke 参与进来后,用 ANTLR 来实现了该语言,并给它取了这个新名字,他后来又使用 JavaCC 重新实现了该语言.目前 OGNL 由 Dr

Struts2框架学习之四:OGNL表达式

前言 OGNL表达式的全称是Object Graph Navigation Language,就是对象导航图语言.使用OGNL表达式可以使用简单的语法设置以及读取Java对象的值,还包括调用对象的方法,实现类型转换等功能.而且,OGNL表达式的强大还不止于此.我们知道在JSP中已经内置了表达式语言,那么使用OGNL表达式的优势是什么呢?主要是语法变得更简单以及功能的更强大.这两点会在后面的内容加以体现. 对象的操作 使用OGNL表达式实现对象的操作,包括以下几个方面: 1.访问JavaBean属

Webwork 学习之路(二)前端OGNL试练

阅读目录 1.OGNL 出现的意义 2.OGNL项目实战 回到顶部 1.OGNL 出现的意义    在mvc中,数据是在各个层次之间进行流转是一个不争的事实.而这种流转,也就会面临一些困境,这些困境,是由于数据在不同世界中的表现形式不同而造成的: a. 数据在页面上是一个扁平的,不带数据类型的字符串,无论你的数据结构有多复杂,数据类型有多丰富,到了展示的时候,全都一视同仁的成为字符串在页面上展现出来. b. 数据在Java世界中可以表现为丰富的数据结构和数据类型,你可以自行定义你喜欢的类,在类与

详解Java的Struts框架中栈值和OGNL的使用_java

值栈:值栈是一个集合中的几个对象保持下列对象提供的顺序: 值栈可以通过JSP,Velocity或者Freemarker的标签.有各种不同的标签在单独的章节中,我们将学习,用于获取和设置Struts 2.0 的值栈. ValueStack的对象里面可以得到动作如下: ActionContext.getContext().getValueStack() 一旦拥有了值对象,就可以用下面的方法来操纵该对象: OGNL:对象图形导航语言(OGNL)是一个功能强大的表达式语言是用来参考值栈上的数据和操纵.

OGNL,简单的struts 2专用表达式,你表达了吗?(10)

OGNL是Object Graphic Navigation Language(对象图导航语言)的缩写,OGNL是一个开源项目,读者可以访问其官方站点www.ognl.org以获得源代码和相关资料.OGNL是一种功能强大的EL(Expression Language,表达式语言),可以通过简单的表达式来访问Java对象中的属性. OGNL先在WebWork项目中得到应用,也是Struts 2框架视图默认的表达式语言,可以说,OGNL表达式是Struts 2框架的特点之一.   8.1.3  一个

struts2之OGNL和struts2标签库和ValueStack对象

OGNL简介: (1)OGNL是Object Graphic Navigation Language(对象图导航语言)的缩写,它是一个开源项目.  struts2框架默认就支持Ognl表达式语言(所以struts必须引用的包:ognl.jar): (2)struts2的ognl的作用:页面取值使用: (3)OGNL和EL的区别: EL表达式语言:用于页面取值,jsp页面取值的标准(默认可以直接使用,应用范围更加广泛):   OGNL表达式语言:struts2标签默认支持的表达式语言,必须配置st

win7系统右下角小旗子强大的作用

合理的利用win7系统在的功能,那么你根本不需要装其他方面的软件,对于新手基本上装个360安全卫士就行了.今天我们要来说的是win7操作中心的功能,也就是win7系统右下角小旗子的强大功能. 1 win7操作中心 在win7操作系统中,当我们把鼠标移到并单击右下角的小旗时,我们就会看到下图: 2 小编感觉功能比较好的就是这个图,它可以提醒我们系统中存在哪些安全隐患,还有那些功能需要维护.很多时候通过这边的设置能让你电脑的可被利用漏洞变得更少,让病毒无法破坏甚至进入你的系统. 3 win7操作中心

draggabilly一款功能强大的拖动拖拽元素插件

draggabilly一款功能强大的拖动拖拽元素插件:http://download.csdn.net/detail/cometwo/9411895 支持移动触摸设备的纯js元素拖放插件 :http://download.csdn.net/detail/cometwo/9411907 :"` <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" con