OGNL的三要素
OGNL(Object Graph Navigation Language)是一个开源的表达式引擎。通过使用OGNL,我们能够通过表达式存取Java对象树中的任意属性和调用Java对象树的方法等。也就是说,如果我们把表达式看成是一个带有语义的字符串,那么OGNL就是这个语义字符串与Java对象之间沟通的催化剂。通过OGNL,我么可以轻松解决在数据流转过程中所遇到的各种问题。
表达式
表达式(Expression)是整个OGNL的核心,所有OGNL操作都是针对表达式解析后进行的。表达式会规定此次OGNL操作到底要“干什么”。因此,表达式其实是一个带有语法含义的字符串,这个字符串将规定操作的类型和操作的内容。
OGNL支持大量的表达式语法,不仅支持“链式”描述对象访问路径,还支持在表达式中进行简单的计算,甚至还能够支持复杂的Lambda表达式等。
Root对象
OGNL的Root对象(Root Object)可以理解为OGNL的操作对象。当OGNL表达式规定了“干什么”以后,我们还需要指定“对谁干”。OGNL的Root对象实际上是一个Java对象,是所有OGNL操作的实际载体。这就意味着,如果有一个OGNL的表达式,那么我们实际上需要针对Root对象来进行OGNL表达式的计算并返回结果。
上下文环境
有了表达式和Root对象,我们已经可以使用OGNL的基本功能了。例如,根据表达式针对OGNL中的Root对象进行“取值”或者“写值”操作。不过,事实上,在OGNL的内部,所有的操作都会在一个特定的数据环境中运行,这个数据环境就是OGNL的上下文环境(Context),即这个上下文环境将规定OGNL的操作“在哪里干”
OGNL的上下文环境是一个Map结构,称之为OgnlContext。之前我们所提到的Root对象,事实上也会被添加到上下文环境中,并且被作为一个特殊的变量进行处理。
OGNL的基本操作
对Root对象的访问
针对OGNL的Root对象的对象树的访问时通过“ . ”(点号)将对象的引用串联起来实现的。通过这种方式,OGNL实际上将一个树形的对象结构转化成一个链式结构的字符串来表达语义。
//获取Root对象中的name属性的值
name
//获取Root对象department属性中name属性的实际值
department.name
//获取Root对象department属性的manager属性中name属性的实际值
department.manager.name
对上下文环境的访问
由于OGNL的上下文是一个Map结构,在OGNL进行计算时可以事先在上下文环境中设置一些参数,并让OGNL将这些参数带入进行计算。有时候也需要对这些上下文环境中的参数进行访问,访问这些参数时,需要通过“#”符号加上链式表达式来进行,从而表示与访问Root对象的区别。如下所示:
// 获取OGNL上下文环境中名为introduction的对象的值
#introduction
// 获取OGNL上下文环境中名为parameters的对象中user对象中名为name的属性的值
#parameters.user.name
对静态变量的访问
在OGNL中,对于静态变量或者静态方法的访问,需要通过 @[class]@[field / method] 的表达式语法来进行。如下所示:
// 访问com.example.core.Resource类中名为ENABLE的属性值
@com.example.core.Resource@ENABLE
// 调用com.example.core.Resource类中名为get的方法
@com.example.core.Resource@get()
方法调用
在OGNL中调用方法,可以直接通过类似Java的方法调用方式进行,也就是通过“ . ”(点号)加方法名称完成方法调用,甚至可以传递参数。如下所示:
// 调用Root对象中的group属性中users的size()方法
group.users.size()
// 调用Root对象中group中的containsUser的方法,并将上下文环境中名为requestUser的值作为参数传入
group.containsUser(#requestUser)
使用操作符进行简单计算
OGNL表达式中能使用的操作符基本与Java里的操作符一样,除了能使用+、-、*、/、++、–、==等操作符之外,还能使用mod、in、not in 等。如下所示:
2 + 4 // 加
'hello' + 'world' //字符串叠加
5 - 3 // 减
9 / 2 // 除
9 mod 2 // 取模
foo++ //递增
foo == bar // 等于判断
foo in list // 是否在容器中
对数组和容器的访问
OGNL表达式可以支持对数组按照数组下标的顺序进行访问。同样的方法可以用于有序的容器,如ArrayList、LinkedHashSet等。对于Map结构,OGNL支持根据键值进行访问。如下所示:
// 访问Root对象的group属性中users的第一个对象的name属性值
group.users[0].name
// 访问OGNL上下文中名为sessionMap的Map对象中key为currentLogonUser
#sessionMap['currentLogonUser']
投影与选择
OGNL支持类似于数据库中的投影(projection)和选择(selection)功能
投影操作是指选出集合中每个元素的相同属性组成新的集合,类似于关系数据库的字段操作。投影操作语法为 collection.{XXX} ,其中 XXX 就是这个集合中每个元素的公共属性。
选择就是过滤满足 selection 条件的集合元素,类似于关系数据库的结果集操作。选择操作的语法为: collection.{X YYY} ,其中 X 是一个选择操作符,后面则是选择用的逻辑表达式。选择操作符由三种:
? 选择满足条件的所有元素
^ 选择满足条件的第一个元素
$ 选择满足条件的最后一个元素
如下所示:
// 返回Root对象的group属性中users这个集合中所有元素的name构成的集合
group.users.{name} // 新的以name为元素的集合
// 将group中users这个集合中的元素的code和name用-连接符拼起来构成的字符串集合
group.users.{code + '-' + name} // 新的以'code-name'为元素的集合
// 返回Root对象的group中users这个集合所有元素中name不为null的元素构成的集合
group.users.{? #this.name != null} // 过滤后的users集合
构造对象
OGNL支持直接通过表达式来构造对象。构造的方式主要包括3种:
构造List: 使用 {} ,中间使用“ , ”(逗号)隔开元素的方式来表达列表
构造Map:使用 #{} ,中间使用“ , ”(逗号)隔开键值对,并使用冒号隔开key和value来构造Map
构造对象:直接使用已知对象的构造函数来构造对象
构造对象的方法如下所示:
// 构造一个List
{"green", "red", "blue"}
// 构造一个Map
#{"key1" : "value1", "key2" : "value2", "key3" : "value3"}
// 构造一个java.net.URL对象
new java.net.URL("http://localhost/")
构造对象对于表达式语言来说是一个非常强大的功能,OGNL不仅能够直接对容器对象构造提供语法层面的支持,还能够对任意的Java对象提供支持。这样一来就使得OGNL不仅仅具备了数据运算这一简单的功能,同时还被赋予了潜在的逻辑计算功能。
深入this指针
我们知道,OGNL表达式是以“ . ”(点号)进行串联的一个链式字符串表达式。而这个表达式在进行计算的时候,从左到右,表达式每一次计算返回的结果成为一个临时的“当前对象”,并在此临时对象之上继续进行计算,直到得到计算结果。而这个临时的“当前对象”会被存储在一个叫做this的变量中,这个this变量就称为this指针。
在OGNL表达式中的this指针,无疑指向了当前计算的调用者对应的实例。 需要注意的是,如果试图在表达式中使用this指针,需要在this之前加上“#”,如下面例子:
// 返回group中users这个集合中所有age比3大的元素构成的集合
users.{? #this.age > 3}
// 返回group中users这个集合里的大小+1的值
group.users.size().(#this+ 1)
// 返回Root对象的group中users这个集合所有元素中name不为null的元素构成的集合
group.users.{? #this.name != null}
有关#符号的三种用途
在之前的要点中已经展示了“#”的几种不同用途。在这里在详细总结为:
加在普通OGNL表达式前面,用于访问OGNL上下文中的变量
使用 #{} 语法动态构建Map
加在 this指针 之前表示对this指针的引用
参考:整理归纳自《struts2技术内幕——深入解析struts2架构设计与实现原理》