背景
前段時間在我的技術羣裏,大家討論起了爲什麼 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中的方法是絕對不能重載的。