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也专门限制了这种歧义