欢迎投稿

今日深度:

警惕,MyBatis的size()方法竟然有坑!,size方法

警惕,MyBatis的size()方法竟然有坑!,size方法


警惕,MyBatis的size()方法竟然有坑!

来源:http://h5ip.cn/aJgJ

Mybatis是一个开源的轻量级半自动化ORM框架,使得面向对象应用程序与关系大乐透追加倍投多少钱的映射变得更加容易。MyBatis使用xml描述符或注解将对象与存储过程或SQL语句相结合。Mybatis最大优点是应用程序与Sql进行解耦,sql语句是写在Xml Mapper文件中。OGNL表达式在Mybatis当中应用非常广泛,其表达式的灵活性使得动态Sql功能的非常强大。OGNL是Object-Graph Navigation Language的缩写,代表对象图导航语言。OGNL是一种EL表达式语言,用于设置和获取Java对象的属性,并且可以对列表进行投影选择以及执行lambda表达式。Ognl类提供了许多简便方法用于执行表达式的。Struts2发布的每个版本都会出现的新的高危可执行漏洞也是因为它使用了灵活的OGNL表达式。公司后端采用Mybatis作为数据访问层,所使用版本为3.2.3。线上环境业务系统在运行过程中出现了一个令人困惑的异常, 该异常时而出现时而不出现,构造各种OGNL表达式为空等特殊情况均不会重现该异常。具体异常堆栈信息如下:

  1. ###?Error?querying?database.??Cause:?org.apache.ibatis.builder.BuilderException:?Error?evaluating?expression?'list?!=?null?and?list.size()?>?0'.?Cause:?org.apache.ibatis.ognl.MethodFailedException:?Method?"size"?failed?for?object?[1]?[java.lang.IllegalAccessException:?Class?org.apache.ibatis.ognl.OgnlRuntime?can?not?access?a?member?of?class?java.util.Collections$SingletonList?with?modifiers?"public"]?
  2. ###?Cause:?org.apache.ibatis.builder.BuilderException:?Error?evaluating?expression?'list?!=?null?and?list.size()?>?0'.?Cause:?org.apache.ibatis.ognl.MethodFailedException:?Method?"size"?failed?for?object?[1]?[java.lang.IllegalAccessException:?Class?org.apache.ibatis.ognl.OgnlRuntime?can?not?access?a?member?of?class?java.util.Collections$SingletonList?with?modifiers?"public"]?
  3. ????at?org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:23)?org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:107)?
  4. ????at?org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:98)?
  5. ????at?cn.com.shaobingmm.MybatisBugTest$2.run(MybatisBugTest.java:88)?
  6. ????at?java.lang.Thread.run(Thread.java:745)?
  7. Caused?by:?org.apache.ibatis.builder.BuilderException:?Error?evaluating?expression?'list?!=?null?and?list.size()?>?0'.?Cause:?org.apache.ibatis.ognl.MethodFailedException:?Method?"size"?failed?for?object?[1]?[java.lang.IllegalAccessException:?Class?org.apache.ibatis.ognl.OgnlRuntime?can?not?access?a?member?of?class?java.util.Collections$SingletonList?with?modifiers?"public"]?
  8. ????at?org.apache.ibatis.scripting.xmltags.OgnlCache.getValue(OgnlCache.java?
  9. ????at:47)?
  10. ????at?org.apache.ibatis.scripting.xmltags.ExpressionEvaluator.evaluateBoolean(ExpressionEvaluator.java:29)?
  11. ????at?org.apache.ibatis.scripting.xmltags.IfSqlNode.apply(IfSqlNode.java:30)?
  12. ????at?org.apache.ibatis.scripting.xmltags.MixedSqlNode.apply(MixedSqlNode.java:29)?
  13. ????at?org.apache.ibatis.scripting.xmltags.TrimSqlNode.apply(TrimSqlNode.java:51)?
  14. ????at?org.apache.ibatis.scripting.xmltags.MixedSqlNode.apply(MixedSqlNode.java:29)?
  15. ????at?org.apache.ibatis.scripting.xmltags.DynamicSqlSource.getBoundSql(DynamicSqlSource.java:37)?
  16. ????at?org.apache.ibatis.mapping.MappedStatement.getBoundSql(MappedStatement.java:275)?
  17. ????at?org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:79)?
  18. ????at?org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:104)?
  19. ????...?3?more?
  20. Caused?by:?org.apache.ibatis.ognl.MethodFailedException:?Method?"size"?failed?for?object?[1]?[java.lang.IllegalAccessException:?Class?org.apache.ibatis.ognl.OgnlRuntime?can?not?access?a?member?of?class?java.util.Collections$SingletonList?with?modifiers?"public"]?
  21. ????at?org.apache.ibatis.ognl.OgnlRuntime.callAppropriateMethod(OgnlRuntime.java:837)?
  22. ????at?org.apache.ibatis.ognl.ObjectMethodAccessor.callMethod(ObjectMethodAccessor.java:61)?
  23. ????at?org.apache.ibatis.ognl.OgnlRuntime.callMethod(OgnlRuntime.java:860)?
  24. ????at?org.apache.ibatis.ognl.ASTMethod.getValueBody(ASTMethod.java:73)?
  25. ????at?org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:170)?
  26. ????at?org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:210)?
  27. ????at?org.apache.ibatis.ognl.ASTChain.getValueBody(ASTChain.java:109)?
  28. ????at?org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:170)?
  29. ????at?org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:210)?
  30. ????at?org.apache.ibatis.ognl.ASTGreater.getValueBody(ASTGreater.java:49)?
  31. ????at?org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:170)?
  32. ????at?org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:210)?
  33. ????at?org.apache.ibatis.ognl.ASTAnd.getValueBody(ASTAnd.java:56)?
  34. ????at?org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:170)?
  35. ????at?org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:210)?
  36. ????at?org.apache.ibatis.ognl.Ognl.getValue(Ognl.java:333)?
  37. ????at?org.apache.ibatis.ognl.Ognl.getValue(Ognl.java:413)?
  38. ????at?org.apache.ibatis.ognl.Ognl.getValue(Ognl.java:395)?
  39. ????at?org.apache.ibatis.scripting.xmltags.OgnlCache.getValue(OgnlCache.java:45)?
  40. ????...?12?more?

List的size()方法明显是public为何还会出现不可访问的异常。该问题并不是每一次都会出现,经过多次尝试,该异常一直未在测试环境重现。该接口在完整调用链路中的出错次数占总调用次数的比率为0.01%,无意中联想到并发问题在周期性时间内往往是概率性发生。编写模拟多线程环境并发读取公司列表测试代码:

  1. ?
  2. ?????
  3. ????????select?*?
  4. ????????from?company?
  5. ?????????
  6. ?????????????0">?
  7. ????????????????and?id?in?
  8. ???????#{id}?
  9. ?
  10. ?????????????
  11. ?????????
  12. ?????
  13. ?

多线程并发环境下的压测代码

  1. String?resource?=?"mybatis-config.xml";?
  2. ????????InputStream?in?=?null;?
  3. ????????try?{?
  4. ????????????in?=?Resources.getResourceAsStream(resource);?
  5. ????????????SqlSessionFactory?sqlSessionFactory?=?new?SqlSessionFactoryBuilder().build(in);?
  6. ????????????final?List?ids?=?Collections.singletonList(1L);?
  7. ????????????final?SqlSession?session?=?sqlSessionFactory.openSession();?
  8. ????????????final?CountDownLatch?mCountDownLatch?=?new?CountDownLatch(1);?
  9. ????????????for?(int?i?=?0;?i?
  10. ????????????????Thread?thread?=?new?Thread(new?Runnable()?{?
  11. ????????????????????public?void?run()?{?
  12. ????????????????????????try?{?
  13. ????????????????????????????mCountDownLatch.await();?
  14. ????????????????????????}?catch?(InterruptedException?e)?{?
  15. ????????????????????????????e.printStackTrace();?
  16. ????????????????????????}?
  17. ????????????????????????for?(int?k?=?0;?k?
  18. ????????????????????????????session.selectList("CompanyMapper.getCompanysByIds",?ids);?
  19. ????????????????????????}?
  20. ????????????????????}?
  21. ????????????????});?
  22. ????????????????thread.start();?
  23. ????????????}?
  24. ????????????mCountDownLatch.countDown();?
  25. ????????????synchronized?(MybatisBugTest.class)?{?
  26. ????????????????try?{?
  27. ????????????????????MybatisBugTest.class.wait();?
  28. ????????????????}?catch?(InterruptedException?e)?{?
  29. ????????????????????e.printStackTrace();?
  30. ????????????????}?
  31. ????????????}?
  32. ?
  33. ????????}?catch?(IOException?e)?{?
  34. ????????????e.printStackTrace();?
  35. ????????}?catch?(Throwable?e)?{?
  36. ????????????e.printStackTrace();?
  37. ????????}?finally?{?
  38. ????????????if?(in?!=?null)?
  39. ????????????????try?{?
  40. ????????????????????in.close();?
  41. ????????????????}?catch?(IOException?e)?{?
  42. ????????????????????e.printStackTrace();?
  43. ????????????????}?
  44. ????????}?

上诉异常堆栈信息在并发环境下果然重现出现,根据异常信息代码执行至该行代码时发生异常:

  1. Caused?by:?org.apache.ibatis.ognl.MethodFailedException:?Method?"size"?failed?for?object?[1]?[java.lang.IllegalAccessException:?Class?org.apache.ibatis.ognl.OgnlRuntime?can?not?access?a?member?of?class?java.util.Collections$SingletonList?with?modifiers?"public"]?
  2. ????at?org.apache.ibatis.ognl.OgnlRuntime.callAppropriateMethod(OgnlRuntime.java:837)?

异常信息表明OgnlRuntime类不能够访问java.util.Collections的私有成员SingletonList。查看源代码发现能够抛出MethodFailedException异常可以锁定在invokeMethod方法内部。

  1. public?static?Object?callAppropriateMethod(OgnlContext?context,?Object?source,?Object?target,?String?methodName,?String?propertyName,?List?methods,?Object[]?args)?throws?MethodFailedException?{?
  2. ????????Object?reason?=?null;?
  3. ????????Object[]?actualArgs?=?objectArrayPool.create(args.length);?
  4. ?
  5. ????????try?{?
  6. ????????????Method?e?=?getAppropriateMethod(context,?source,?target,?methodName,?propertyName,?methods,?args,?actualArgs);?
  7. ????????????if(e?==?null?||?!isMethodAccessible(context,?source,?e,?propertyName))?{?
  8. ????????????????StringBuffer?buffer?=?new?StringBuffer();?
  9. ????????????????if(args?!=?null)?{?
  10. ????????????????????int?i?=?0;?
  11. ?
  12. ????????????????????for(int?ilast?=?args.length?-?1;?i?<=?ilast;?++i)?{?
  13. ????????????????????????Object?arg?=?args[i];?
  14. ????????????????????????buffer.append(arg?==?null?NULL_STRING:arg.getClass().getName());?
  15. ????????????????????????if(i?
  16. ????????????????????????????buffer.append(",?");?
  17. ????????????????????????}?
  18. ????????????????????}?
  19. ????????????????}?
  20. ?
  21. ????????????????throw?new?NoSuchMethodException(methodName?+?"("?+?buffer?+?")");?
  22. ????????????}?
  23. ?
  24. ????????????Object?var14?=?invokeMethod(target,?e,?actualArgs);?
  25. ????????????return?var14;?
  26. ????????}?catch?(NoSuchMethodException?var21)?{?
  27. ????????????reason?=?var21;?
  28. ????????}?catch?(IllegalAccessException?var22)?{?
  29. ????????????reason?=?var22;?
  30. ????????}?catch?(InvocationTargetException?var23)?{?
  31. ????????????reason?=?var23.getTargetException();?
  32. ????????}?finally?{?
  33. ????????????objectArrayPool.recycle(actualArgs);?
  34. ????????}?
  35. ?
  36. ????????throw?new?MethodFailedException(source,?methodName,?(Throwable)reason);?
  37. ????}?

invokeMethod方法代码

  1. public?static?Object?invokeMethod(Object?target,?Method?method,?Object[]?argsArray)?throws?InvocationTargetException,?IllegalAccessException?{?
  2. ????????boolean?wasAccessible?=?true;?
  3. ????????if(securityManager?!=?null)?{?
  4. ????????????try?{?
  5. ????????????????securityManager.checkPermission(getPermission(method));?
  6. ????????????}?catch?(SecurityException?var6)?{?
  7. ????????????????throw?new?IllegalAccessException("Method?["?+?method?+?"]?cannot?be?accessed.");?
  8. ????????????}?
  9. ????????}?
  10. ?
  11. ????????if((!Modifier.isPublic(method.getModifiers())?||?!Modifier.isPublic(method.getDeclaringClass().getModifiers()))?&&?!(wasAccessible?=?method.isAccessible()))?{?
  12. ????????????method.setAccessible(true);?(1)?
  13. ????????}?
  14. ?
  15. ????????Object?result?=?method.invoke(target,?argsArray);?(3)?
  16. ????????if(!wasAccessible)?{?
  17. ????????????method.setAccessible(false);?(2)?
  18. ????????}?
  19. ?
  20. ????????return?result;?
  21. ????}?

问题出现在method实际上是一个共享变量,也就是例子中的

  1. public?int?java.util.Collections$SingletonList.size()?

方法

当第一个线程t1至(1)行代码允许method方法可以被调用,第二个线程t2执行至(2)将method的方法设置为不可以访问。接着t1又开始执行到(3)行的时候就会发生该异常。这是一个很典型的同步问题。Ognl2.7已经修复了该问题,因为ognl源码是直接打包内嵌在mybatis包中,mybatis3.3.0版本中也已经进行了修复升级。(划重点)

  1. public?static?Object?invokeMethod(Object?target,?Method?method,?Object[]?argsArray)?throws?InvocationTargetException,?IllegalAccessException?{?
  2. ????????boolean?syncInvoke?=?false;?
  3. ????????boolean?checkPermission?=?false;?
  4. ????????int?mHash?=?method.hashCode();?
  5. ????????synchronized(method)?{?
  6. ????????????if(_methodAccessCache.get(Integer.valueOf(mHash))?==?null?||?_methodAccessCache.get(Integer.valueOf(mHash))?==?Boolean.TRUE)?{?
  7. ????????????????syncInvoke?=?true;?
  8. ????????????}?
  9. ?
  10. ????????????if(_securityManager?!=?null?&&?_methodPermCache.get(Integer.valueOf(mHash))?==?null?||?_methodPermCache.get(Integer.valueOf(mHash))?==?Boolean.FALSE)?{?
  11. ????????????????checkPermission?=?true;?
  12. ????????????}?
  13. ????????}?
  14. ?
  15. ????????boolean?wasAccessible?=?true;?
  16. ????????Object?result;?
  17. ????????if(syncInvoke)?{?
  18. ????????????synchronized(method)?{?
  19. ????????????????if(checkPermission)?{?
  20. ????????????????????try?{?
  21. ????????????????????????_securityManager.checkPermission(getPermission(method));?
  22. ????????????????????????_methodPermCache.put(Integer.valueOf(mHash),?Boolean.TRUE);?
  23. ????????????????????}?catch?(SecurityException?var12)?{?
  24. ????????????????????????_methodPermCache.put(Integer.valueOf(mHash),?Boolean.FALSE);?
  25. ????????????????????????throw?new?IllegalAccessException("Method?["?+?method?+?"]?cannot?be?accessed.");?
  26. ????????????????????}?
  27. ????????????????}?
  28. ?
  29. ????????????????if(Modifier.isPublic(method.getModifiers())?&&?Modifier.isPublic(method.getDeclaringClass().getModifiers()))?{?
  30. ????????????????????_methodAccessCache.put(Integer.valueOf(mHash),?Boolean.FALSE);?
  31. ????????????????}?else?if(!(wasAccessible?=?method.isAccessible()))?{?
  32. ????????????????????method.setAccessible(true);?
  33. ????????????????????_methodAccessCache.put(Integer.valueOf(mHash),?Boolean.TRUE);?
  34. ????????????????}?else?{?
  35. ????????????????????_methodAccessCache.put(Integer.valueOf(mHash),?Boolean.FALSE);?
  36. ????????????????}?
  37. ?
  38. ????????????????result?=?method.invoke(target,?argsArray);?
  39. ????????????????if(!wasAccessible)?{?
  40. ????????????????????method.setAccessible(false);?
  41. ????????????????}?
  42. ????????????}?
  43. ????????}?else?{?
  44. ????????????if(checkPermission)?{?
  45. ????????????????try?{?
  46. ????????????????????_securityManager.checkPermission(getPermission(method));?
  47. ????????????????????_methodPermCache.put(Integer.valueOf(mHash),?Boolean.TRUE);?
  48. ????????????????}?catch?(SecurityException?var11)?{?
  49. ????????????????????_methodPermCache.put(Integer.valueOf(mHash),?Boolean.FALSE);?
  50. ????????????????????throw?new?IllegalAccessException("Method?["?+?method?+?"]?cannot?be?accessed.");?
  51. ????????????????}?
  52. ????????????}?
  53. ?
  54. ????????????result?=?method.invoke(target,?argsArray);?
  55. ????????}?
  56. ?
  57. ????????return?result;?
  58. ????}?
www.htsjk.Com true http://www.htsjk.com/shujukukf/35617.html NewsArticle 警惕,MyBatis的size()方法竟然有坑!,size方法 来源:http://h5ip.cn/aJgJ Mybatis是一个开源的轻量级半自动化ORM框架,使得面向对象应用程序与关系大乐透追加倍投多少钱的映射变得更加容易。MyBatis使用xm...
评论暂时关闭