一、背景
最近在工作之余,把mybatis的源码看了下,决定自己手写个简单版的。实现核心的功能即可。写完之后,执行了一下,正巧在mybatis对Mapper接口的动态代理这个核心代码这边发现一个问题。正好再回头看下jdk的动态代理,才发现问题所在。
二、问题
当我用SqlSession.getMapper() 方法来获取Mapper的代理类的时候,发现这个代理对象所展示的toString()是个null。如下图
而debug了一下mybatis的源码,发现是有值,并且的确是new 代理的目标对象为MapperProxy类型
三、回头看jdk动态代理
当我在排查问题的时候,无意中发现,在执行(T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);方法时,竟然会抛出 mapperProxy类的invoke方法中的异常,但是这个时候程序还没有走到任何代理的方法。
回头找了下之前学着写的动态代理的例子,并在invoke方法里面输出了method。如下:
启动main方法,发现打印的内容:
这时候才发现,在实例化代理类的时候,会调用一次invoke()方法,并且此时调用方法的method参数是Object.toString() 。看到这发现了两点
- invoke方法在实例化代理的时候会调用一次,如果这个方法有对全局的公共变量做修改的话,会存在隐患的问题
- 代理类的toString 最后出来的类的名称就是调用这个方法得到的。将代码中result输出就是对应的代理类。
四、发现并解决问题
回到自己的手写的mybatis源码中,自然就定位到了如下这个地方:
对比一下mybatis的源码
正是红框中的代码,自己手写的时候认为代理的方法的声明类都是定义的mapper接口,这边的判断其实没有必要,也走不到。然而从第三点中可以知道,在new 代理类的时候传入的Method为toString方法正好进了这个分支,并且调用了当前MapperProxy实例的toString方法,返回了代理类的名称,正是之前所缺失的。把这段代码加上就OK了