从Mybatis源码理解jdk动态代理默认调用invoke方法

时间:2021-10-05 16:16:07

一、背景
最近在工作之余,把开mybatis的源码看了下,决定自己手写个简单版的。实现核心的功能即可。写完之后,执行了一下,正巧在mybatis对Mapper接口的动态代理这个核心代码这边发现一个问题。正好再回头看了下jdk的动态代理发现问题所在。

二、问题
问题所在,当我用SqlSession.getMapper() 方法来获取Mapper的代理类的时候,发现这个代理对象所展示的toString()是个null。如下图从Mybatis源码理解jdk动态代理默认调用invoke方法

而debug了一下mybatis的源码发,发现是有值,并且的确是new 的MapperProxy类型的对象

从Mybatis源码理解jdk动态代理默认调用invoke方法

三、回头看jdk动态代理
当我在排查问题的时候,无意中发现,在执行(T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);方法时,竟然会抛出 mapperProxy类的invoke方法中的异常,但是这个时候程序还没有走到任何代理的方法。
回头找了下之前学着写的动态代理的例子,并在invoke方法里面输出了method。如下:

从Mybatis源码理解jdk动态代理默认调用invoke方法

从Mybatis源码理解jdk动态代理默认调用invoke方法

启动main方法,发现打印的内容:

从Mybatis源码理解jdk动态代理默认调用invoke方法

这时候才发现,在实例化代理类的时候,会调用一次invoke()方法,并且此时调用方法的method参数是Object.toString() 。看到这发现了两点

  1. invoke方法在实例化代理的时候会调用一次,如果这个方法有对全局的公共变量做修改的话,会存在隐患的问题
  2. 代理类的toString 最后出来的类的名称就是调用这个方法得到的。将代码中result输出就是对应的代理类。

四、发现并解决问题
回到自己的手写的mybatis源码中,自然就定位到了如下这个地方:

从Mybatis源码理解jdk动态代理默认调用invoke方法

对比一下mybatis的源码

从Mybatis源码理解jdk动态代理默认调用invoke方法

正是红框中的代码,自己手写的时候认为代理的方法的声明类都是定义的mapper接口,这边的判断其实没有必要,也走不到。然而从第三点中可以知道,在new 代理类的时候传入的Method为toString方法正好进了这个分支,并且调用了当前MapperProxy实例的toString方法,返回了代理类的名称,正式之前所缺失的。把这段代码加上就OK了

从Mybatis源码理解jdk动态代理默认调用invoke方法