1 background

  • 1.1 Created onepublic interface AbstractDao, as the parent of the business Mapper, interface, used as a common methoddefaultWays to provide:
    public interface AbstractDao {
        public default  <R, P extends PageParam> Page<R> findPage(...).{
            //do something}}Copy the code
  • 1.2 Mybatis version: 3.4.5
  • 1.3 Throwing an exception during the testUndeclaredThrowableException:
      java.lang.reflect.UndeclaredThrowableException
      	at jdk.proxy2/jdk.proxy2.$Proxy28.findPage(Unknown Source)
      	....
      Caused by: java.lang.NoSuchMethodException: java.lang.invoke.MethodHandles$Lookup.<init>(java.lang.Class,int)
      	at java.base/java.lang.Class.getConstructor0(Class.java:3517)
      	at java.base/java.lang.Class.getDeclaredConstructor(Class.java:2691)
      	at org.apache.ibatis.binding.MapperProxy.invokeDefaultMethod(MapperProxy.java:75)
      	at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:53)...2 more
    Copy the code

2 Preliminary Judgment

  • 2.1 MyBatis itself realizes dynamic proxy of Interface method through InvocationHander. The implementation class isorg.apache.ibatis.binding.MapperProxy.
    • MapperProxyFor related reference:MyBatis- A Select query process.
  • 2.2 According to the exception information, MyBatis is runningMapperProxy.invokeAn exception was thrown when.

3 Follow-up questions

  • 3.1 MyBatis 3.4.5 mapPerProxy. invoke implementation handles the default method separately:

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          try {
            if (Object.class.equals(method.getDeclaringClass())) {
              Method is a method declared by the Object class
              return method.invoke(this, args);
            } else if (isDefaultMethod(method)) {
              // Call invokeDefaultMethod.
              // The default findPage defined above is executed here.
              returninvokeDefaultMethod(proxy, method, args); }}catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
          }
          // Get MapperMethod and execute
          final MapperMethod mapperMethod = cachedMapperMethod(method);
          return mapperMethod.execute(sqlSession, args);
        }
        
        // Determine the default value in MapperProxy
        private boolean isDefaultMethod(Method method) {
          return ((method.getModifiers()
              & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC)
              && method.getDeclaringClass().isInterface();
        }
    Copy the code
  • 3.2 Calling the default method

       /* invokeDefaultMethod invokeDefaultMethod invokeDefaultMethod invokeDefaultMethod invokeDefaultMethod invokeDefaultMethod invokeDefaultMethod invokeDefaultMethod invokeDefaultMethod invokeDefaultMethod invokeDefaultMethod invokeDefaultMethod * /
       @UsesJava7
       private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
           throws Throwable {
         /* The constructor to obtain MethodHandles.Lookup from the getDeclaredConstructor method provides two class.class, int. Class */
         final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
             .getDeclaredConstructor(Class.class, int.class); . }Copy the code
    • 3.2.1. Follow up the Class getDeclaredConstructor method, you can see, at the time of execution getConstructor0, if not found in accordance with the above provide the parameter of the constructor, would be thrown NoSuchMethodException:

       private Constructor<T> getConstructor0(Class<? >[] parameterTypes,int which) throws NoSuchMethodException
       {
           ReflectionFactory fact = getReflectionFactory();
           Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC));
           for (Constructor<T> constructor : constructors) {
               if (arrayContentsEq(parameterTypes,
                                   fact.getExecutableSharedParameterTypes(constructor))) {
                   returnconstructor; }}Class
                [] parameterTypes' constructor throws NoSuchMethodException.
           throw new NoSuchMethodException(methodToString("<init>", parameterTypes));
       }
      Copy the code

      If you open MethodHandles.Lookup in JDK8, you can see that there are only two constructors :Lookup(Class
      ) and Lookup (Class
      , Class
      , int), and does not Lookup(Class
      < span style = “max-width: 100%; clear: both; min-height: 1em;

    • Why is thrown UndeclaredThrowableException 3.2.2?

      Open the comment for the InvocationHandler.invoke method and you will see the following description:

      If a checked exception is thrown by this method that is not assignable to any of the exception types declared in the throws clause of the interface method, then an UndeclaredThrowableException containing the exception that was thrown by this method will be thrown by the method invocation on the proxy instance.

      While NoSuchMethodException inheritance is NoSuchMethodException – > ReflectiveOperationException – > Exception, So eventually throws UndeclaredThrowableException.

4 summarizes

  • Mybatis version 3.4.5, in the implementation of interfacedefaultMethod, using the jdk7 constructor argumentMethodHandles.LookupIs thrownNoSuchMethodException, and finally throwsUndeclaredThrowableException.
    • If you encounter this problem in JDK8, just upgrade Mybatis version.
  • 2 When implementing dynamic proxies using InvocationHandler, invoke executes code that throwschecked exceptionIf the exception is not declared in the interface method of the proxyUndeclaredThrowableException; Such asNoSuchMethodException.
    • Want to avoid throwing UndeclaredThrowableException, can invoke in the implementation of the code, the exception toRuntimeExceptionIs thrown in the way.
    • Also throughUndeclaredThrowableException.getUndeclaredThrowable()Get the underlying exception.