参考自 :http://commons.apache.org/proper/commons-ognl/
OGNL简介:
OGNL(Object Graph Navigation Library) 指图对象导航语言。ognl是一个表达式语言,对于java对象属性的get,set ,以及其他附加项,如列表、投影、选择以及lambda表达式使用比较方便。在获取和设置属性值时使用相同的表达式。
ognl到底能做些什么呢?下面给出一些应用:
java能做的,ognl大部分可以完成,以及一些其他操作,比如列表,投影,选择,lambda表达式。
下面是使用指南:
基本的ognl表达式是比较简单的。语言已经变得非常丰富,具有很多特性,但是您一般不需要担心语言中更复杂的部分。下面是一个简单的case。例如要获取返回的headline对象中属性的text属性,ognl表达式:headline.text
那么属性到底是什么呢?大致上,ognl属性与bean属性相同。看下面完整的解释:
ognl表达式的基本单位是调用链,通常称为“链”。最简单的链由以下部分组成:
表达式 | 例子 |
---|---|
属性 | 例如 headline.text 返回当前对象的值 |
方法调用 | 例如 hashcode() 返回当前对象的哈希码 |
数组索引 | 例如 listeners[0] 返回当前对象的第一个元素 |
所有OGNL表达式都在当前对象的上下文中进行计算,并且链只是使用链中前一个链接的结果作为下一个链接的当前对象。您可以根据需要扩展链。例如,这个链:
name.toCharArray()[0].numericValue.toString()
这个表达式遵循以下步骤来解析:
name
属性(用户通过ongl
上下文提供给ognl
);string
上调用toCharArray()
方法;numbericValue
属性(该字符表示为 Character
对象,Character
类有一个名为getNumericValue()
的方法 );toString()
。此表达式的最终结果是最后一次toString()
调用返回的String。请注意,此示例只能用于从对象获取值,而不能用于设置值。通过以上表达式到Ognl.setValue()
方法将导致 InappropriateExpressionException
被抛出,因为在链中的最后一个环节是既不是属性名字和一个数组索引。
这些语法足够您完成需要做的大部分事情。
本节概述了ognl表达式元素的细节。
常量(Constants)
ognl 有以下几种常量:
属性引用(Referring to Properties)
OGNL在处理属性引用时以不同的方式处理不同类型的对象。Maps将所有属性引用视为元素查找或存储,并将属性名称作为键。List 和 数组 以相同的方式处理数字属性,属性名称作为索引,但字符串属性与普通对象相同。普通对象(即所有其他类型)只能使用“get”和“set”方法(或“is”和“set”)方法或者通过他们的名字调用。
请注意这里的新术语。属性“名称”可以是任何类型,而不仅仅是字符串。但是要引用非String属性,必须使用我们称之为“索引”表示法的内容。例如,要获取数组的长度,可以使用以下表达式:
array.length
但要获取数组的元素0,必须使用如下表达式:
array[0]
请注意,Java集合具有一些与之关联的特殊属性。
索引(Indexing)
如上所述,“索引”符号实际上只是属性引用,尽管是计算形式的属性引用而不是常量引用。
例如,OGNL在内部将"array.length"
表达式与此表达式完全相同:
array["length"]
这个表达式会有相同的结果(虽然内部形式不一样):
array [ "len" + "gth" ]
数组和列表索引(Array and List Indexing)
对于Java数组和列表,索引非常简单,就像在Java中一样。给出整数索引,该元素是引用。如果索引超出了数组的范围或者抛出了IndexOutOfBoundsException,就像在Java中一样。
JavaBeans属性索引(JavaBeans Indexed Properties)
JavaBeans支持索引属性的概念。具体来说,这意味着对象具有一组遵循以下模式的方法:
public PropertyType[] getPropertyName();
public void setPropertyName(PropertyType[] anArray);
public PropertyType getPropertyName(int index);
public void setPropertyName(int index, PropertyType value);
OGNL可以解释这一点,并通过索引表示法提供对属性的无缝访问。参考如
someProperty[2]
自动路由通过正确的索引属性访问器(在上面的例子中通过 getSomeProperty(2)
或setSomeProperty(2,value)
。如果没有索引属性访问器,则会找到名为someProperty的属性,并将索引应用于该属性。
OGNL对象属性索引(OGNL Object Indexed Properties)
OGNL扩展了索引属性的概念,包括使用任意对象进行索引,而不仅仅是像JavaBeans属性索引一样的整数。当找到属性作为对象索引的候选者时,OGNL会查找具有以下签名的方法模式:
public PropertyType getPropertyName(IndexType index);
public void setPropertyName(IndexType index, PropertyType value);
该属性类型和的IndexType
必须完全匹配set
和 get
方法。一个实际示例是Servlet API:Session
对象有两种获取和设置任意属性的方法:
public Object getAttribute(String name) public void setAttribute(String name, Object value)
可以获取和设置其中一个属性的OGNL表达式是:
session.attribute["foo"]
OGNL调用方法与Java的方式略有不同,因为 OGNL被解释并且必须在运行时选择正确的方法,除了提供的实际参数之外没有额外的类型信息。OGNL总是选择能找到的最具体的方法,其类型与提供的参数匹配; 如果有两个或多个方法具有相同的特定性并且与给定的参数匹配,则其中一个将被任意选择。
特别是,null参数匹配所有非基本类型,因此最有可能导致调用意外方法。
请注意,方法的参数由逗号分隔,因此除非括在括号中,否则不能使用逗号运算符。例如,
method( ensureLoaded(), name )
是对2个参数方法的调用,而
method( (ensureLoaded(), name) )
是对1个参数方法的调用。
OGNL有一个简单的变量方案,它允许您存储中间结果并再次使用它们,或者只是命名事物以使表达式更容易理解。OGNL 中的所有变量对整个表达式都是全局的。您可以#来定义变量,如下所示: #var
OGNL还将当前对象存储在此变量中表达式求值的每个点上,其中它可以像任何其他变量一样被引用。
例如,以下表达式对侦听器的数量进行操作,如果超过100,则返回两倍的数字,否则返回与20的和:
listeners.size().(#this > 100 ? 2*#this : 20+#this)
ognl方法调用可以使用定义初始变量的map可以使用定义变量初始值的映射来调用OGNL。调用OGNL的标准方法是定义变量root(包含初始或根,对象)和上下文(它包含变量本身的Map)。
要显式地为变量赋值,只需在左侧写一个带有变量引用的赋值语句:
#var = 99
括号表达式(Parenthetical Expressions)
正如您所期望的那样,括在括号中的表达式将作为一个单元进行运算,与周围的运算符分开。括号表达式可以修改ognl运算符优先级顺序。这也是在方法参数中使用逗号运算符的唯一方法。
链式子表达式(Chained Subexpressions)
如果在点后使用括号表达式,则点上当前的对象将用作整个括号表达式中的当前对象。例如, headline.parent.(ensureLoaded(), name)
遍历标题和父属性,确保加载父级,然后返回(或设置)父级的名称。顶级表达式也可以这种方式链接。表达式的结果是最右边的表达式元素。ensureLoaded(), name
这将在根对象上调用ensureLoaded()
,然后获取根对象的name
属性作为表达式的结果。
集合构造(Collection Construction)
Lists
要创建对象列表,请在花括号中包含表达式列表。与方法参数一样,这些表达式不能使用逗号运算符,除非它括在括号中。这是一个例子:
name in { null,"Untitled" }
这将测试name属性是null还是等于“Untitled”。
上述语法将创建List接口的实例。确切的子类未定义。
本地数组(Native Arrays)
有时您想要创建Java数组,例如int []或Integer []。OGNL支持创建这些类似于通常调用构造函数的方式,也允许从现有列表或给定大小的数组初始化本机数组。
new int[] { 1, 2, 3 }
这将创建一个由三个整数1,2和3组成的新int数组。要创建包含所有null或0元素的数组,请使用替代大小构造函数
new int[5]
这将创建一个包含5个插槽的int数组,所有插槽都初始化为零。
Maps
也可以使用特殊语法创建Map。
#{ "foo" : "foo value", "bar" : "bar value" }
这将创建一个使用“foo”和“bar”的映射进行初始化的Map 。希望选择特定Map类的高级用户可以在开始大括号之前指定该类
#@java.util.LinkedHashMap@{ "foo" : "foo value", "bar" : "bar value" }
上面的示例将创建JDK 1.4类LinkedHashMap的实例,确保保留元素的插入顺序。
跨集合投影
OGNL提供了一种简单的方法来调用相同的方法或从集合中的每个元素中提取相同的属性,并将结果存储在新的集合中。我们从数据库术语中称之为“投影”,用于从表中选择列的子集。例如,这个表达式:
listeners.{delegate}
返回所有侦听器委托的列表。有关OGNL如何将各种对象视为集合,请参阅强制部分。在投影期间,#this变量引用迭代的当前元素。
objects.{ #this instanceof String ? #this : #this.toString()}
以上将从对象列表中生成一个新的字符串列表。
从集合中选择(Selecting From Collections)
OGNL提供了一种使用表达式从集合中选择一些元素并将结果保存在新集合中的简单方法。我们从数据库术语中称之为“选择”,用于从表中选择行的子集。例如,这个表达式:
listeners.{? #this instanceof ActionListener}
返回所有那些作为ActionListener类实例的侦听器的列表。
选择匹配到的第一个元素(Selecting First Match)
为了从匹配列表中获取第一个匹配项,您可以使用索引,例如 listeners.{? true }[0]
。但是,这很麻烦,因为如果匹配不返回任何结果(或者如果结果列表为空),您将获得ArrayIndexOutOfBoundsException
。选择语法也可用于仅选择第一个匹配并将其作为列表返回。如果匹配对于任何元素都不成功,则结果为空列表。
objects.{^ #this instanceof String }
将返回包含在作为String类实例的对象中的第一个元素。
选择最后匹配(Selecting Last Match)
与第一个匹配相似,有时您希望得到最后一个匹配的元素。
objects.{$ #this instanceof String }
这将返回包含在作为String类实例的对象中的最后一个元素
您可以使用new运算符在Java中创建新对象。一个区别是您必须为java.lang包中的类以外的类指定完全限定的类名。这仅适用于默认的ClassResolver
。
使用自定义类解析程序包可以映射,以便可以对类进行更多类似Java的引用。有关使用ClassResolver类的详细信息,请参阅OGNL Developer’s Guide (例如,新的java.util.ArrayList(),而不仅仅是新的ArrayList())。
您可以使用 @class@method(args)
语法调用静态方法。
如果省略类,则默认为java.lang.Math
,以便更容易调用min
和max
方法。
如果指定了类,则必须提供完全限定的名称。
如果您有一个要调用其静态方法的类的实例,则可以通过该对象调用该方法,就像它是一个实例方法一样。
如果方法名称被重载,OGNL将使用与重载实例方法相同的过程来选择正确的静态方法。
您可以使用 @class@field
语法引用静态字段。该课程必须完全合格。
如果您使用带括号的表达式跟随OGNL表达式,而在括号前面没有点,OGNL将尝试将第一个表达式的结果视为要评估的另一个表达式,并将使用带括号的表达式的结果作为根对象对于那次评估。第一个表达式的结果可以是任何对象; 如果它是AST,OGNL假定它是表达式的解析形式并简单地解释它; 否则,OGNL获取对象的字符串值并解析该字符串以获取要解释的AST。
例如,这个表达式 #fact(30H)
查找事实变量,并使用BigInteger表示30作为 根对象将该变量的值解释为OGNL表达式。有关使用返回其参数的阶乘的表达式设置事实变量的示例,请参见下文。请注意,此双精度求值运算符和方法调用之间的OGNL语法存在歧义。OGNL通过调用看起来像方法调用,方法调用的任何东西来解决这种歧义。例如,如果当前对象具有持有OGNL的fact属性 因子表达式,您无法使用此方法来调用它
fact(30H)
因为OGNL会将此解释为对事实方法的调用。您可以通过括号括起属性引用来强制执行所需的解释:(fact)(30H)
OGNL具有简化的lambda表达式语法,可以编写简单的函数。它不是一个完全成熟的演算,因为没有关闭—在所有变量OGNL具有全局范围和程度。例如,这是一个OGNL表达式,它声明了一个递归因子函数,然后调用它:
#fact = :[#this<=1? 1 : #this*#fact(#this-1)], #fact(30H)
lambda表达式是括号内的所有内容。的#this变量保存的参数的表达,这是最初30H,每一次调用之后减1。
OGNL将lambda表达式视为常量。一个lambda表达式的值是AST该OGNL使用作为包含表达的解析形式。
OGNL提供了一些特殊的集合属性。原因是集合不遵循JavaBeans模式进行方法命名; 因此必须调用size()
, length()
等方法,而不是更直观地将这些方法称为属性。
OGNL通过公开某些伪属性来纠正这一点,就好像它们是内置的一样。
集合 | 特殊属性 |
---|---|
集合(被Map,List,Set 继承) | size : 集合元素的个数 isEmpty : 如果集合是空的返回 true |
List |
iterator: 通过 Iterator 遍历 List |
Map |
keys : 返回map 的key 集合 values: 返回map的值集合 注意: someMap["size"] 获取size 的key , someMap.size 返回map 的 元素个数 |
Set |
iterator: 通过 Iterator 遍历 Set |
Iterator |
next : 从Iterator 获取到下一个元素; hasNext : 判断 Iterator 里是否有下一个元素 |
Enumeration |
next : 通过枚举获取下一个元素;hasNext : 是否有下一个元素;nextElement : 与next 作用相同;hasMoreElements : 与hasNext 作用相同 |
在大多数情况下,OGNL的操作是从Java借来的,并且与Java的操作类似。有关完整的讨论,请参阅OGNL参考。在这里,我们描述了 不是Java的OGNL运算符,或者与Java不同的OGNL运算符。逗号(,)或叫做 序列运算符。此运算符从C借用。逗号用于分隔两个独立的表达式。第二个表达式的值是逗号表达式的值。这是一个例子:
ensureLoaded(), name
在计算此表达式时,将调用ensureLoaded方法(可能是为了确保对象的所有部分都在内存中),然后检索name属性(如果获取值)或替换(如果设置)。列出带花括号的结构({})。您可以通过将值括在花括号中来在线创建列表,如下例所示:
{ null ,true ,false }
下面的not in 操作。这是一个包含测试,用于查看某个值是否在集合中。例如,
name in {null,"Untitled"} || name
如前所述,由于表达式的性质,一些可获取的值也不可设置。例如, names[0].location
是一个可设置的表达式 - 表达式的最后一个组件解析为可设置的属性。
但是,有些表达方式如 names[0].length + 1
不可设置,因为它们不解析为对象中的可设置属性。它只是一个计算值。如果试图评估使用任何的这个表达Ognl.setValue()方法将失败,并抛出InappropriateExpressionException。也可以使用包含’ = '运算符的get表达式设置变量。当get表达式需要将变量设置为执行的结果时,这很有用。
这里我们描述OGNL如何将对象解释为各种类型。请参阅下文,了解OGNL如何将对象强制为布尔值,数字,整数和集合。
将对象解释为布尔值(Interpreting Objects as Booleans)
可以在需要布尔值的地方使用任何对象。OGNL将对象解释为这样的布尔值:
将对象解释为数字(Interpreting Objects as Numbers)
数值运算符试图将它们的参数视为数字。基本的原始类型包装类(Integer
,Double
等,包括Character
和Boolean
,它们被视为整数),以及java.math
包中的大数字类(BigInteger
和BigDecimal
)被识别为特殊数字类型。给定其他类的对象,OGNL尝试将对象的字符串值解析为数字。
采用两个参数的数字运算符使用以下算法来确定结果应该是什么类型。如果结果不适合给定类型,则实际结果的类型可能更宽。
将对象解释为整数(Interpreting Objects as Integers)
仅对整数运算的运算符(如位移运算符)将其参数视为数字,除了BigDecimal和BigInteger作为BigInteger运行 ,所有其他类型的数字作为Long运行。对于BigInteger情况,这些运算符的结果仍然是BigInteger ; 对于 Long的情况,结果表示为相同类型的参数(如果适合),否则表示为Long。
将对象解释为集合(Interpreting Objects as Collections)
投影和选择运算符(e1.{e2} and e1.{?e2} )和in运算符都将它们的一个参数视为集合并进行处理。这取决于参数的类别而有所不同:
Java数组从前到后走;
通过遍历迭代器来遍历java.util.Collection的成员;
java.util.Map的成员遍历它们的值的遍历迭代器;
java.util.Iterator和java.util.Enumeration的成员通过迭代来遍历;
java.lang.Number的成员通过返回小于零的给定数字的整数来“遍历”;
所有其他对象都被视为仅包含自身的单例集合。
附录:OGNL语言参考
本节对OGNL的语法和实现进行了相当详细的处理。请参见下面的完整表OGNL的运算符优先级。
OGNL运算符大多从Java中借来,并增加了一些新的运算符。在大多数情况下,OGNL对给定运算符的处理与Java相同,但重要的是要注意 OGNL本质上是一种无类型语言。这意味着OGNL中的每个值都是一个Java对象,而OGNL试图从每个对象强制使用与其所使用的情况相适应的含义(参见强制部分)。
下表按OGNL运算符。如果同一个框中列出了多个运算符,则这些运算符具有相同的优先级,并按从左到右的顺序进行计算。