|
在使用mybatis框架时,我们发现mapper接口是没有实现类的,取而代之的是一个xml文件。也就是说我们调用mapper接口,实际上是使用了对应的mapper.xml中定义sql语句完成数据操作的。
那么我们有没有想过,为什么mapper接口没有实现类,它是如何和xml关联起来的?
我们在学习java 基础时,知道接口是不可以直接调用的,只有通过定义实现类了实现接口,然后new一个实现类对象,再将实现类对象赋给接口声明的对象。调用接口的方法实际上就是调用被引用对象的方法,也就是实现类实现的方法。那么为啥Mybatis的接口不需要实现类呢?
下面我们就以查询所有员工数据为例,进行一步一步的调试,查看底层是怎么操作的呢?
EmployeeMapper接口
EmployeeMapper.xml文件的sql语句
那么getAllEmp方法被调用的时候,被引用的对象是谁呢?接口被调用时候发生了什么?
我们先来回答第二个问题,既然找不到实现类,EmployeeMapper有没可能被代理起来呢,getAllEmp方法调用时候,我们找到代理对象来执行就行了。
在学习代理模式时,有一个动态代理模式,通过动态代理接口的所有方法,每次接口被调用,就会进入动态代理对象的invoke方法,然后加载xml中的sql完成操作数据库,再返回结果。
那么Mybatis 的Mapper接口有没有可能被动态代理对象来实现,完成后面的操作呢?
接下来我们进入debug模式来调试看看。
(1) 在sqlSession.getMapper方法这里设置一个断点
(2)继续走到Configuration类的getMapper方法里,Configuration主要存储 Mybatis 所有的配置信息,包括mybatis配置文件、EmployeeMapper.xml等文件都会被预先加载到Configuration里。
这时候,我们发现Configuration里面出现了一个mapperRegistry,这个是mapper的注册器,其实在加载EmployeeMapper.xml的时候,我们就需要在mapperRegistry里面进行注册,所以,我们可以从这里进行获取。继续走~
(3)进入到mapperRegistry的getMapper方法
这里分为了两步执行:
(1)knownMappers.get(type);
获取已知的加载过的mapper中获取出mapper代理工厂
(2)mapperProxyFactory.newInstance(sqlSession);
代理工厂生成动态代理返回
那么knownMappers其实是个map集合,根据EmployeeMapper.class获取MapperProxyFactory,所以knownMappers必然是源码前面的步骤中set进去的。我们先找找,到底是哪里set进去的呢,继续调试我们发现XMLMapperBuilder类的bindMapperForNamespace方法
这个方法里面的boundType,是通过Resources.classForName(namespace);生成的class,Resources.classForName底层其实就是调用Class.forName生成的反射对象,而参数是namespace,namespacne就是com.mybatis3.mappers.EmployeeMapper:
Class.forName(com.mybatis3.mappers.EmployeeMapper)生成反射对象。生成的boundType在被configuration.addMapper(boundType);可以看到在这里调用了put方法放进去的。
(4) 接下来再回到mapperRegistry的getMapper方法继续往下调试进入到MapperProxyFactory的newInstance里面,看到该方法return newInstance(mapperProxy)回来,继续往下调试
(5)进入到newInstance这个方法里面我们看到了Proxy.newProxyInstance代码,就是JDK创建代理的方法
到这里我们发现是调用了JDK动态代理的newProxyInstance方法。
(6)那么我们来看看是如何代理的,打开MapperProxy这个类,找到里面的invoke方法
在这个方法里面MapperMethod 类有两个重点,其一就是初始化,其二就是执行SQL,为什么说初始化很重要呢?因为初始化的时候需要通过接口名全称+方法全称去 Configuration 找我们之前加载的SQL,这也就是为什么接口方法定义和SQL的ID必须保持一致的原因;其二执行SQL。
(7)接着MapperMethod 调用 execute 执行 SQL,代码很简单如下
在Case:SELECT下 我们找到
result = this.executeForMany(sqlSession, args);
(8)我们点开executeForMany方法
最终我们发现是调用了sqlSession.selectList方法,然后根据方法名找到EmployeeMapper.xml文件中对应id标识,执行sql语句完成了查询操作。
总结
今天我们从认为Mapper接口是创建出来的代理对象完成了查询操作出发,一步步进行了调试发现,最终Mybatis的接口不需要实现类,实际上是使用了JDK动态代理的方式创建代理对象完成的。 |
|