1.OGNL概述
OGNL是一种功能强大的开源表达式语言。使用这种表达式语言可以通过某种表达式语法获取Java对象的任意属性,调用Java对象的方法,以及实现类型转换等。
2.OGNL三要素
OGNL的操作实际上是围绕OGNL结构的三个要素进行,分别是表达式(expression)、上下文对象(context)和根对象(root)。
2.1.表达式
表达式是整个OGNL的核心,OGNL会根据表达式到对象中取值。所有OGNL操作都是针对表达式解析后进行的,它表明了此次OGNL操作要“做什么”。实际上,表达式就是一个带有语法含义的字符串,这个字符串规定了操作的类型和操作的内容。
2.2.上下文对象
上下文对象规定了OGNL操作“在哪里进行”。context对象是一个Map类型的对象,在表达式中访问context中的对象,需要使用#号加对象名称,即“#对象名称”的形式。
例如要获取context对象中user对象的username值,可以如下书写:
#user.username
2.3.根对象
根对象可以理解为OGNL的操作对象,OGNL可以对根对象进行取值或写值等操作,表达式规定了“做什么”,而根对象则规定了“对谁操作”。实际上根对象所在的环境就是OGNL的上下文对象环境。
3.OGNL表达式语法
从以下方面对OGNL表达式基本语法和用法进行详细介绍。
完整的语法说明参考https://commons.apache.org/proper/commons-ognl/language-guide.html。
3.1.特殊符号
主要有以下三种特殊符号。
3.1.1.特殊符号——#
符号“#”有三种用法:
(1)访问非根对象属性
(2)用于过滤和投影集合
(3)用来构造Map
3.1.2.特殊符号——%
用于在标识的属性为字符串类型是,计算OGNL表达式的值,例如:
<s:url value="test.jsp?age=#userlist['admin']">→test.jsp?#userlist['admin']---可见当字符串与OGNL表达式串起来时,只会被当作字符串对待,并不执行
<s:url value="test.jsp?age=%{#userlist['admin']}">→test.jsp?age=44---使用了该符号,就可以使得OGNL表达式被执行
3.1.3.特殊符号——$
两个用途:
(1)用于在国际化资源文件中,引用OGNL表达式。
(2)在struts2配置文件中引用OGNL表达式。
3.2.常量
OGNL表达式规定有以下5种常量:
(1)以单引号或双引号括起来的字符串
(2)以单引号括起来的字符
(3)数字常量,除了Java语法中的常量外,OGNL还定义了以B或者b结尾的BigDecimals以及以H或者h结尾的BigIntegers(为了避免和BigDecimals的后缀搞混,OGNL特意取了Huge这个单词的首字母作为BigIntegers的后缀)
(4)布尔常量:true和false
(5)null常量
3.3.操作符——[]
OGNL通过“[]”来支持对象应用的高阶用法。
3.3.1.区别重载方法
通过[]引用入参,比如某个对象存在以下四个重载方法:
public PropertyType[] getPropertyName();
public void setPropertyName(PropertyType[] anArray);
public PropertyType getPropertyName(int index);
public void setPropertyName(int index, PropertyType value);
则someProperty[2]等价于Java代码的getPropertyName(2)
3.3.2.动态计算
name.length就等价于name['length'],也等价于name['len' + 'th']
[]的引入可以让引用对象属性更加灵活。
3.4.方法调用
举例:
method( ensureLoaded(), name )
有两个地方需要注意,OGNL是运行时调用,因此没有任何静态类型的信息可以参考,所以如果解析到有多个匹配的方法,则任选其中一个方法调用。
常量null可以匹配所有的非原始类型的对象。
3.5.复杂链式表达式
如果在.后面链接一个括号表达式,则当前表达式计算的结果会传入括号中,比如:
headline.parent.(ensureLoaded(), name)
等价于
headline.parent.ensureLoaded(), headline.parent.name
3.6.变量引用
临时变量在OGNL是在变量名前加#来表示的,虽然是临时变量,但是他们都是全局可见的。此外,表达式计算的每一步结果都存在变量this中,比如下面这个例子:
listeners.size().(#this > 100? 2*#this : 20+#this)
表达的含义就是如果listeners数量大于100则返回2倍的值,否则返回数量加20
3.7.括号表达式
括号的作用Java语法中一致,会始终被当成一个语法单元进行解析,当然也会改变运算符的先后顺序,比如在下面的方法调用中,会首先解析括号(ensureLoaded(), name)的值(逗号表达式的含义同Java语法规则),然后才会执行方法调用
method( (ensureLoaded(), name) )
3.8.集合操作
主要有以下集合操作。
3.8.1.新建列表Lists
比如:name in { null,"Untitled" }
这条语句判断name是否等于null或者"Untitled"
3.8.2.新建原生数组
(1)创建数组并初始化
new int[] { 1, 2, 3 }
(2)创建数组并指定长度
new int[5]
3.8.3.新建Maps
(1)新建普通Map
#{ "foo" : "foo value", "bar" : "bar value" }
(2)新建特定类型Map
#@java.util.LinkedHashMap@{ "foo" : "foo value", "bar" : "bar value" }
3.8.4.集合的投影
OGNL把对针对集合上的每个元素调用同一个方法并返回新的集合的行为称之为“投影”,类似于Java Stream中的map比如:
listeners.{delegate}
将这个集合每个元素的delegate属性组成了新的集合并返回
objects.{ #this instanceof String ? #this : #this.toString()}
返回objects中元素的String形式
3.8.5.查找集合元素
(1)查找所有匹配的元素
listeners.{? #this instanceof ActionListener}
(2)查找第一个匹配的元素
objects.{^ #this instanceof String }
(3)查找最后一个匹配的元素
objects.{$ #this instanceof String }
3.8.6.集合的虚拟属性
在Java的语法中,想要观察集合的状态必须要调用相应的集合方法,比如说size()或者是length()等,OGNL定义了一些特定的集合属性,含义与相应的集合方法完全等价。
(1)Collections
size集合的大小
isEmpty集合为空时返回true
(2)List
iterator返回List的Iterator
(3)Map
keys返回Map的所有key值
values返回Map的所有value值
(4)Set
iterator返回Set的Iterator
3.9.类的静态方法
有以下几种用法。
3.9.1.构造函数
非java.lang包下的所有类的构造函数都要用类的全限定名称
new java.util.ArrayList()
3.9.2.静态方法
@class@method(args)
如果省略class则默认引用的类是java.lang.Math除此之外,还可以通过类的实例引用静态方法
3.9.3.静态属性
@class@field
3.10.伪lambda表达式
有以下几种用法。
3.10.1.表达式值计算
#fact(30H)
如果括号运算符前面没有.运算符,则OGNL会将括号中的结果传到括号前的表达式中作为起始值,但是需要注意和方法调用的区别:
fact(30H)
上面这样写会调用当前上下文的fact方法,并传入参数30H。
如果想强制进行表达式计算,可以像下面这样:
(fact)(30H)
3.10.2.伪lambda表达式
因为OGNL的变量都是全局可见的,所以只能实现一个简化的lambda表达式,例如:
#fact = :[#this<=1? 1 : #this*#fact(#this-1)], #fact(30H)