背景
前段时间在我的技术群里,大家讨论起了为什么UserMapper.java是个接口,没有具体实现类,而我们可以直接调用其方法?
关于这个问题,我之前面试过一些人,很多人是这么回答的:
1.我领导叫我们使用
Mybatis,大家都这么用就这么用了(没想过,反正就这么用)。2.虽然我不知道具体是怎么实现的,但我觉得肯定是……(此处略去若干的漫天猜想),但是也不对啊,难道是……(再次略去若干似懂非懂)。
3.使用动态代理实现的(然后就没有下文了)。
对于上面的三种回答,前面两种我们就没必要往下聊了。
但是第三种回答,就有必要往下问:那你说说动态代理有哪些实现方式?Mybatis使用的是哪一种?
如果这个问题你还能回答上来,那么还会继续问:UserMapper.java中大方法能不能重载?
如果你能回答上面的问题,本文就没必要往下看了,已经不适合你了。
问题分析
先来看一张图,这图里的代码就是我们前面写的 demo:
为什么一个接口就能和一个 xml 文件给绑定的呢?这就是今天我们要聊的话题。
可能很多小伙伴不熟悉 ibatis,2010 年之前,还没有Mybatis,之后 ibatis 便成了现在的Mybatis,如果有兴趣的朋友,可以看到Mybatis中的包目录。
这个包目录中就还是 ibatis,并且 ibatis 的作者现在就在腾讯上班,开发英雄联盟 LOL。
如果有腾讯的小伙伴可以打听打听哈,大佬就在身边。言归正传。
Mapper 层在
Mybatis中现在是接口形式就搞定了,而在 ibatis 时代还是必须要有实现类的,我记得 2012 年的时候,使用的就是 ibatis,Dao(Mapper)必须要有实现类。
下面我们就来看看Mybatis中是怎么做的。
使用案例
继续使用我们上一节中的代码。
controller
service 实现类中
打一个断点,然后使用 debug 模式启动项目。并访问:
http://localhost:9002/test
userMapper=org.apache.ibatis.binding.MapperProxy@6da21078
发现Mybatis给UserMapper.java生成了一个代理对象,并且从名字上可以看出是 JDK 动态代理。
关于动态代理请,这里我推荐我之前写过的一篇文章:
其实,又差不多回到了 ibatis 时代,只是 Mybatis 中是通过动态代理的方式生成的代理类不是我们开发的,而是通过 JDK 动态代理生成的代理类。
下面我们也使用 JDK 动态代理来模拟一把。
public class MapperProxy implements InvocationHandler { @SuppressWarnings("unchecked") public <T> T newInstance(Class<T> clz) { return (T) Proxy.newProxyInstance(clz.getClassLoader(), new Class[] { clz }, this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { try { // 诸如hashCode()、toString()、equals()等方法,将target指向当前对象this return method.invoke(this, args); } catch (Throwable t) { } } // 投鞭断流 return new User((Integer) args[0], "田维常", 22); }}
再写一个测试类
import com.tian.mybatis.entity.User;import com.tian.mybatis.mapper.UserMapper;public class TestProxy { public static void main(String[] args) { MapperProxy proxy = new MapperProxy(); UserMapper mapper = proxy.newInstance(UserMapper.class); User user = mapper.selectById(999); System.out.println(user); System.out.println(mapper.toString()); }}
输出
User{id=999, userName='田维常', age=22, gender=null}com.tian.mybatis.proxy.MapperProxy@39a054a5
这便是Mybatis自动映射器 Mapper 的底层实现原理。
但是在Mybatis中,远远不是这么简单的,但是本质就是这样的。
下面我们就来大致分析一下Mybatis中的这个流程。
接口 Mapper 内的方法能重载吗?
类似下面:
public User getUserById(Integer id);public User getUserById(Integer id, String name);
答案:不能
因为Mybatis中是使用 package+Mapper+method 全限名作为 key,去 xml 内寻找唯一 sql 来执行的。
类似:key=com.tian.mybatis.UserMapper.getUserById,那么,重载方法时将导致矛盾。
对于 Mapper 接口,Mybatis禁止方法重载(overLoad) 。
在 MapperMethod 类的静态内部类中 SqlCommand 中有个 resolveMappedStatement 方法。
在 Configuration 中有个属性,就是项目启动的时候,会把 Mapper.xml 中信息解析到这个属性里,以我们指定的 namespace+method 作为 key 放到 Map 里面,后面我们调用 Mapper 接口动态类的某个方法时候再去 map 获取。
protected final Map<String, MappedStatement> mappedStatements
就是使用类的全路径名.方法作为 key 存放到 Map 中的。
总结
常用动态代理方式:JDK 动态代理和 CGlib 动态代理。
Mybatis 是采用 JDK 动态代理+反射+xml 来解决接口绑定的,为我们创建可以调用的代理对象。
我们的 Mapper 中的方法是绝对不能重载的。