Mybatis is an open source lightweight semi-automated ORM framework that makes it easier for object-oriented applications to map to relational databases. MyBatis uses XML descriptors or annotations to combine objects with stored procedures or SQL statements. The biggest advantage of Mybatis is that the application is decoupled from Sql, which is written in an Xml Mapper file. OGNL expression is widely used in Mybatis, and the flexibility of its expression makes dynamic Sql function very powerful. OGNL is an abbreviation for Object-Graph Navigation Language, which stands for Object Graph Navigation Language. OGNL is an EL expression language for setting and getting properties of Java objects, and for projecting selections on lists and performing lambda expressions. The Ognl class provides a number of handy methods for executing expressions. The use of flexible OGNL expressions is also responsible for the new high-risk executable vulnerabilities that appear with every release of Struts2. The company backend uses Mybatis as the data access layer, version 3.2.3 is used. A confusing exception appears in the running process of the online environment business system, which sometimes appears and sometimes does not appear, and cannot be repeated in special situations such as constructing various OGNL expressions that are empty. The specific exception stack information is as follows:

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”]

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”]

at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:23) org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:107)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:98)
at cn.com.shaobingmm.MybatisBugTest$2.run(MybatisBugTest.java:88)
at java.lang.Thread.run(Thread.java:745)
Copy the code

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.CollectionsSingletonList with modifiers “public”] at org.apache.ibatis.ognl.OgnlRuntime.callAppropriateMethod(OgnlRuntime.java:837) at org.apache.ibatis.ognl.ObjectMethodAccessor.callMethod(ObjectMethodAccessor.java:61) at org.apache.ibatis.ognl.OgnlRuntime.callMethod(OgnlRuntime.java:860) at org.apache.ibatis.ognl.ASTMethod.getValueBody(ASTMethod.java:73) at org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:170) at org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:210) at org.apache.ibatis.ognl.ASTChain.getValueBody(ASTChain.java:109) at org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:170) at org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:210) at org.apache.ibatis.ognl.ASTGreater.getValueBody(ASTGreater.java:49) at org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:170) at org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:210) at org.apache.ibatis.ognl.ASTAnd.getValueBody(ASTAnd.java:56) at org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:170) at org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:210) at org.apache.ibatis.ognl.Ognl.getValue(Ognl.java:333) at org.apache.ibatis.ognl.Ognl.getValue(Ognl.java:413) at org.apache.ibatis.ognl.Ognl.getValue(Ognl.java:395) at org.apache.ibatis.scripting.xmltags.OgnlCache.getValue(OgnlCache.java:45) … 12 The size() method of the more List is not accessible. This problem did not occur every time, and after several attempts, the exception did not recur in the test environment. The error times of the interface in the complete call link account for 0.01% of the total call times, and the accidental concurrent problems often occur probabilistically in periodic time. Write test code to simulate multi-threaded environment to read company list concurrently: < select id = “getCompanysByIds” resultType = “cn.com.shaobingmm.Company” > select * from company and id # {id} in multi-threaded concurrent environment pressure test code

String resource = “mybatis-config.xml”; InputStream in = null; try { in = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in); final List ids = Collections.singletonList(1L); final SqlSession session = sqlSessionFactory.openSession(); final CountDownLatch mCountDownLatch = new CountDownLatch(1); for (int i = 0; i < 50; i++) { Thread thread = new Thread(new Runnable() { public void run() { try { mCountDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } for (int k = 0; k < 100; k++) { session.selectList(“CompanyMapper.getCompanysByIds”, ids); }}}); thread.start(); } mCountDownLatch.countDown(); synchronized (MybatisBugTest.class) { try { MybatisBugTest.class.wait(); } catch (InterruptedException e) { e.printStackTrace(); }}

    } catch (IOException e) {
        e.printStackTrace();
    } catch (Throwable e) {
        e.printStackTrace();
    } finally {
        if (in != null)
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
    }
Copy the code

Appeal exception stack information under the concurrent environment indeed as expected return, according to the code execution of exception information to the exception occurs when the bank code: under 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”] at org.apache.ibatis.ognl.OgnlRuntime.callAppropriateMethod(OgnlRuntime.java:837)

Exception information indicates that the OgnlRuntime class cannot access the private member SingletonList of Java.util. Collections. A look at the source code shows that the ability to throw a MethodFailedException can be locked inside the invokeMethod method.

public static Object callAppropriateMethod(OgnlContext context, Object source, Object target, String methodName, String propertyName, List methods, Object[] args) throws MethodFailedException { Object reason = null; Object[] actualArgs = objectArrayPool.create(args.length);

try { Method e = getAppropriateMethod(context, source, target, methodName, propertyName, methods, args, actualArgs); if(e == null || ! isMethodAccessible(context, source, e, propertyName)) { StringBuffer buffer = new StringBuffer(); if(args ! = null) { int i = 0; for(int ilast = args.length - 1; i <= ilast; ++i) { Object arg = args[i]; buffer.append(arg == null? NULL_STRING:arg.getClass().getName()); if(i < ilast) { buffer.append(", "); } } } throw new NoSuchMethodException(methodName + "(" + buffer + ")"); } Object var14 = invokeMethod(target, e, actualArgs); return var14; } catch (NoSuchMethodException var21) { reason = var21; } catch (IllegalAccessException var22) { reason = var22; } catch (InvocationTargetException var23) { reason = var23.getTargetException(); } finally { objectArrayPool.recycle(actualArgs); } throw new MethodFailedException(source, methodName, (Throwable)reason); }Copy the code

Public static Object invokeMethod(Object target, Method Method, Object[] argsArray) throws InvocationTargetException, IllegalAccessException { boolean wasAccessible = true; if(securityManager ! = null) { try { securityManager.checkPermission(getPermission(method)); } catch (SecurityException var6) { throw new IllegalAccessException(“Method [” + method + “] cannot be accessed.”); }}

if((! Modifier.isPublic(method.getModifiers()) || ! Modifier.isPublic(method.getDeclaringClass().getModifiers())) && ! (wasAccessible = method.isAccessible())) { method.setAccessible(true); Object result = method.invoke(target, argsArray); (3) if(! wasAccessible) { method.setAccessible(false); (2) } return result; }Copy the code

The problem is that method is actually a shared variable, as in the example

Public int java.util.collections $singletonlist.size () when the first thread T1 through (1) allows method methods to be called, the second thread T2 through (2) sets method methods inaccessible. The exception then occurs when T1 starts executing again at line (3). This is a classic synchronization problem. This issue has been fixed in OGNL 2.7 as the OGNL source code is packaged directly into the Mybatis package and has been fixed and upgraded in Mybatis 3.3.0. Public static Object invokeMethod(Object target, Method Method, Object[] argsArray) throws InvocationTargetException, IllegalAccessException { boolean syncInvoke = false; boolean checkPermission = false; int mHash = method.hashCode(); synchronized(method) { if(_methodAccessCache.get(Integer.valueOf(mHash)) == null || _methodAccessCache.get(Integer.valueOf(mHash)) == Boolean.TRUE) { syncInvoke = true; }

if(_securityManager ! = null && _methodPermCache.get(Integer.valueOf(mHash)) == null || _methodPermCache.get(Integer.valueOf(mHash)) == Boolean.FALSE) { checkPermission = true; } } boolean wasAccessible = true; Object result; if(syncInvoke) { synchronized(method) { if(checkPermission) { try { _securityManager.checkPermission(getPermission(method)); _methodPermCache.put(Integer.valueOf(mHash), Boolean.TRUE); } catch (SecurityException var12) { _methodPermCache.put(Integer.valueOf(mHash), Boolean.FALSE); throw new IllegalAccessException("Method [" + method + "] cannot be accessed."); } } if(Modifier.isPublic(method.getModifiers()) && Modifier.isPublic(method.getDeclaringClass().getModifiers())) { _methodAccessCache.put(Integer.valueOf(mHash), Boolean.FALSE); } else if(! (wasAccessible = method.isAccessible())) { method.setAccessible(true); _methodAccessCache.put(Integer.valueOf(mHash), Boolean.TRUE); } else { _methodAccessCache.put(Integer.valueOf(mHash), Boolean.FALSE); } result = method.invoke(target, argsArray); if(! wasAccessible) { method.setAccessible(false); } } } else { if(checkPermission) { try { _securityManager.checkPermission(getPermission(method)); _methodPermCache.put(Integer.valueOf(mHash), Boolean.TRUE); } catch (SecurityException var11) { _methodPermCache.put(Integer.valueOf(mHash), Boolean.FALSE); throw new IllegalAccessException("Method [" + method + "] cannot be accessed."); } } result = method.invoke(target, argsArray); } return result; }Copy the code