答了 Mybatis 这个问题后,面试官叫我回去等通知……

67 阅读4分钟

背景

前段时间在我的技术群里,大家讨论起了为什么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

发现MybatisUserMapper.java生成了一个代理对象,并且从名字上可以看出是 JDK 动态代理。

关于动态代理请,这里我推荐我之前写过的一篇文章:

gitbook.cn/m/mazi/act.…

其实,又差不多回到了 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 中的方法是绝对不能重载的。