答了Mybatis這個問題後,面試官叫我回去等通知……

背景

前段時間在個人技術羣裏,你們討論起了爲何UserMapper.java是個接口,沒有具體實現類,而咱們能夠直接調用其方法?java

關於這個問題,我以前面試過一些人,不少人是這麼回答的:git

1.我領導叫咱們使用Mybatis,你們都這麼用就這麼用了(沒想過,反正就這麼用)。面試

2.雖然我不知道具體是怎麼實現的,但我以爲確定是……(此處略去若干的漫天猜測),可是也不對啊,難道是……(再次略去若干似懂非懂)。sql

3.使用動態代理實現的(而後就沒有下文了)。apache

對於上面的三種回答,前面兩種咱們就不必往下聊了。mybatis

可是第三種回答,就有必要往下問:那你說說動態代理有哪些實現方式?Mybatis使用的是哪種?app

若是這個問題你還能回答上來,那麼還會繼續問:UserMapper.java中大方法能不能重載?ide

若是你能回答上面的問題,本文就不必往下看了,已經不適合你了。測試

問題分析

先來看一張圖,這圖裏的代碼就是咱們前面寫的demo:this

圖片

爲何一個接口就能和一個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動態代理。

關於動態代理請,這裏我推薦我以前寫過的一篇文章:

https://gitbook.cn/m/mazi/activity/5d44e35e4fbf44126135c292?sut=c93c00a03b4f11eba07ad99b4dfbdab0&utm_source=chatweixinshare

其實,又差很少回到了ibatis時代,只是Mybatis中是經過動態代理的方式生成的代理類不是咱們開發的,而是經過JDK動態代理生成的代理類。

下面咱們也使用JDK動態代理來模擬一把。

public class MapperProxy implements InvocationHandler {
    @SuppressWarnings("unchecked")
    public <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中的方法是絕對不能重載的。

相關文章
相關標籤/搜索