标签:ted 计算 布尔值 递归 ola 对象 key 中断 stand
OgnlCache.getValue()方法的源码比较简单,思路和我们使用其他表达式框架(如google的aviator)类似,即为已经编译好的表达式构建一个缓存并尝试从该缓存中获取编译后表达式,如果未能获取到则进行编译并存入缓存:
可以看到,表达式被编译成一个Node对象,查看一下接口Node的源码,发现其具有parent、child相关方法,因此可以大胆猜测Node应该是一个树状结构:
使用IDEA自带的功能,我们简单查看一下Node的子类,种类还是挺丰富的,对应了mybatis标签中各式各样的表达式语法:
接下来开始debug,一路前进来到Ognl类的getValue方法,如下图所示:
Debug控制台中的信息证实了我们之前关于Node的猜想:“表达式编译后的Node为一棵树”,展开这颗tree,详细看看树的结构:
由此,我们推测表达式的计算顺序应该是沿着叶子节点向其父节点的顺序逐层递归求值,最终在根节点求得整颗树的值。进入上面图片中断点停留的“Object result = ((Node)tree).getValue(ognlContext, root)”方法,调用栈跳转到SimpleNode的getValue方法,并最终进入同一个类的evaluateGetValueBody方法:
到这里就能明显看出问题了,这段神奇的代码在没有锁等同步措施保护的情况下,直接修改非并发安全的共享成员变量constantValueCalculated、hasConstantValue、constantValue的值,明显的竞态条件,但根据这个并不足以得到产生空指针异常的原因,我们继续向前debug:
根据控制台信息,当前执行的表达式是ASTAnd,由于它并不是一个常量,因此this.isConstant(context)返回false并赋值给成员变量hasConstantValue(它的默认值也是false),可以看到,无论constantValueCalculated值为true还是false,非常量表达式最终都会通过上图中的高亮行来求得表达式的值,所以对于非常量表达式,这段代码其实没有并发安全问题;但反之,对于常量表达式,这段代码有并发安全问题,因为hasConstantValue会被更新为true,那么在并发的情况下,会出现一个线程通过执行上图中131行代码求得常量值,另外一个线程只“看到”了constantValueCalculated和hasConstantValue的最新值true而没有“看到”constantValue的最新值,导致执行135行代码返回null。
可以看到,这段代码逻辑也证实了之前关于表达式树的值计算顺序猜想。在继续debug之前,我们需要思考一下空指针可能出现的位置,回顾一下前面图片中控制台打印的ASTAnd这颗表达式树的结构(为了简单,直接将同一张图片复制在下面):ASTConst这个最有可能为常量表达式的节点出现在叶子节点上,我们假设它为常量表达式,在并发的情况下有可能向ASTProperty表达式返回null值,也就是说,ASTProperty表达式会使用一个null值作为输入条件执行某种计算逻辑,那么空指针异常是否出现在这次计算中呢?
为了验证我们的猜想,向前debug到ASTProperty的表达式计算逻辑处,可以看到此表达式获取到一个property对象后,通过OgnlRuntime.getProperty方法继续获取值,这个行为是不是有点像Map的get()方法?如果是这样,那么this.getProperty方法应该就是调用执行ASTConst表达式来“获取map的key”了。
查看ASTProperty的getProperty方法,果然是调用子表达式(即ASTConst)进行计算,来获取一个key:
至于ASTConst是否为常量表达式,其实很简单,它覆写了父类SimpleNode的isNodeConstant方法,并返回一个布尔值true。而SimpleNode中的isConstant方法就是直接调用并返回isNodeConstant方法的值。因此,ASTConst为一个常量表达式。
总结:当前版本mybatis的ognl引擎在并发执行ASTConst表达式计算时,由于未进行同步控制,导致可能向上返回null值,该null值被ASTProperty作为key从数据对象中进行取值,引发空指针异常。而在mybatis 3.3.0版本中,通过提升内嵌的OGNL引擎版本修复了此问题,修复后SimpleNode的evaluateGetValueBody方法源码如下,通过一个方法内的临时变量constant以及为_hasConstantValue添加volatile关键字保证了不同线程执行该方法时,136行返回的值要么是最新的_constantValue(通过130行代码计算得到,且通过volatile刷新对其他线程可见性),要么是通过136行中执行getValueBody方法获得的。
参考:关于OGNL表达式的这个BUG可以在链接“https://issues.apache.org/jira/browse/OGNL-121”中找到。
标签:ted 计算 布尔值 递归 ola 对象 key 中断 stand
原文地址:https://www.cnblogs.com/manayi/p/14660413.html