OGNL的简介
OGNL是一种表达式语言,用于获取对象的属性或者调用方法。它在Mybatis/Struts(以及漏洞)等涉及到模版的场景中经常使用。
语法这里就不讲了,需要注意的是
- OGNL大部分场景是取属性,不建议new对象
- OGNL不支持类似Groovy的safe-null,比如
user?.role?.name
这样的问号表达式,而SpingEL是支持的 - OGNL定位是XML中的胶水,调试方便程度肯定不如Java,因此尽可能不折腾
从断点开始
在Mybatis中封装了如下的OGNL,测试用例如下
Object a = OgnlCache.getValue("a + 1", Collections.singletonMap("a", 10));
System.out.println("a = " + a);//返回 11
只要在
OgnlCache.getValue
中打上了断点,所有的动态SQL生成过程均可以看见细节
有了上面的断点处,你就可以明白平时很多可能含糊的位置了
假如你在XML中写了如下的语句,是否会有疑问需要!= null
这个语句呢
<if test="example != null">
这时就可以断点,可以发现OgnlCache
更上一层的调用栈是
org.apache.ibatis.scripting.xmltags.ExpressionEvaluator#evaluateBoolean
通过阅读这里的代码,可以发现只要为0或者空,就返回false(类似于Groovy的asBoolean),因此在这里就彻底搞懂了test
标签的底层含义,你的xml可以简写为test="example"
OGNL非常方便扩展DSL,特别是在进行自定义注解时,可以帮助开发者节约很多时间
OGNL底层执行过程
OGNL与其它语言一样,也是从字符串到AST,到最后基于上下文的便利实现的,举个例子
(3 + (4 * 5)) - (a / 4)
将被转换为如下的树
(- (+ 3 (* 4 5) ) (/ a 4))
然后调用如下方法进行树的深度遍历
ognl.SimpleNode#getValue
最终返回结果
相关过程比较复杂,可以看更专业的怎样写一个解释器
线程安全
Mybatis内部使用了一个ConcurrentHashMap
作为AST的cache,OGNL的执行本身是各自的上下文,是线程安全的。
OGNL相关暗坑
OGNL与Map
在ognl中,假如你的map是{'aaa.bbb.ccc' => v1}这样的,那么在求值时,一定要写
map.get('aaa.bbb.ccc')
而不是
map.aaa.bbb.ccc
通过OGNL的ObjectAcessor可以了解到解析详情,此外
StrictMap
也专门限制了这种歧义