MyBatis源碼解析【7】接口式編程

前言

這個分類比較連續,若是這裏看不懂,或者第一次看,請回顧以前的博客html

http://www.cnblogs.com/linkstar/category/1027239.htmlsql

 

修改例子

在咱們實際中咱們常見的一種模式就是隻是書寫mybatis的接口,而並不作mybatis的實現,從而減小了代碼量和一些沒有必要的錯誤。編程

下面咱們繼續修改以前的例子。數組

只須要修改咱們的主要測試類就能夠了session

public class MainTest { public static void main(String[] args) throws Exception { SqlSessionFactory sqlSessionFactory = SqlSessionFactoryBuilderTest.getSqlSessionFactory(); SqlSession session = sqlSessionFactory.openSession(); DemoMapper demoMapper = session.getMapper(DemoMapper.class ); Demo demo = demoMapper.selectDemo(); System.out.println(demo.getValue()); /* try { Demo demo = (Demo) session.selectOne("com.xex.dao.mapper.DemoMapper.selectDemo"); System.out.println(demo.getValue()); } finally { session.close(); }*/

 } }

從例子中咱們能夠看到,在mybatis中,一個沒有實現類的接口能夠正常的被調用,而且直接執行到相對應的sql語句。 mybatis

那麼mybatis是如何實現的呢? app

這個問題就是咱們今天須要學習的重點了。ide

 

大體運行步驟

咱們依舊先從大的方向看學習

DemoMapper demoMapper = session.getMapper(DemoMapper.class);測試

首先是從咱們的產品sqlsession中調用了一個getMapper傳入了一個須要被調用接口的類

這個類的傳入類型這邊會產生疑問,爲何會傳入這樣一個class類型呢?

調用完成以後直接返回了一個接口,又會有疑問,難道這個接口在這個方法裏面被new出來了嗎?

接下來就是接口直接調用方法了。

Demo demo = demoMapper.selectDemo();

調用的方法彷佛看起來和原來咱們寫的普通的接口沒有什麼不一樣

可是這個接口並無實現類!!!

那麼執行爲何會返回結果呢?

這個結果又是如何封裝的呢?

帶着這些問題咱們在仔細往裏面看看。

 

源碼解析

首先進入了DefaultSqlSession

image

image進去

image

而後一直往裏走,

image

這裏的代碼就很神奇了

通常人到這裏就看不懂了(若是你的裝備還不夠好的話)

首先mapperProxyFactory是什麼?

從名字上面咱們把它叫作映射代理工廠。

knownMappers這個呢是一個map

這個map是以class做爲鍵,代理工廠做爲值的一個map

而後第一步就是從這個map中取到咱們的代理工廠

------------------------------------------------------

對於map哪裏來的,map裏面的值是從哪裏來的?是在mybatis讀取xml配置文件的時候加載的,具體我不仔細一步步看這些加載的代碼由於對於咱們當前的最終目的關係不大,有興趣的能夠根據下面的思路去,從

new SqlSessionFactoryBuilder().build(inputStream);開始看看配置文件加載的過程。

一、build中的parser.parse()

二、parseConfiguration

三、mapperElement(root.evalNode("mappers"));

四、String mapperClass = child.getStringAttribute("class");

五、else if (resource == null && url == null && mapperClass != null) {
           Class<?> mapperInterface = Resources.classForName(mapperClass);
           configuration.addMapper(mapperInterface);
六、addMapper一直進去就有咯

------------------------------------------------------

咱們先看看這個代理工廠有什麼用?

return mapperProxyFactory.newInstance(sqlSession);

這個newInstance的名稱是否是熟悉?沒錯就是反射裏面的那個。咱們進去看看

image

final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);

咱們先看MapperProxy這個構造方法,傳入了三個參數,sqlSession,接口和一個map

而後構造方法內部成員變量進行賦值就構造完成了咱們這個代理類。

而後就是重點了。

(T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);

看到這個是否是又有熟悉的感受了?沒錯這個是動態代理的知識。

第一個參數是:類加載器

第二個參數是:接口數組

第三個參數是:代理實例的調用處理程序

由動態代理的知識咱們能夠知道,傳入的代理類代理了這個接口。

也就是說,當這個接口的方法被調用的時候,都會先調用代理類中的invoke方法。

若是不明白爲何,證實你還須要好好學習一下動態代理的知識哦。

 

而後咱們進入MapperProxy類(代理類)

image

很顯然這個類實現了InvocationHandler接口

重寫了invoke方法

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   try {
     if (Object.class.equals(method.getDeclaringClass())) {
       return method.invoke(this, args);
     } else if (isDefaultMethod(method)) {
       return invokeDefaultMethod(proxy, method, args);
     }
   } catch (Throwable t) {
     throw ExceptionUtil.unwrapThrowable(t);
   }
   final MapperMethod mapperMethod = cachedMapperMethod(method);
   return mapperMethod.execute(sqlSession, args);
}

第一個參數:代理實例(不須要管,沒用到)

第二個參數:當咱們調用接口方法時,由於有代理類因此會調用invoke方法同時將調用的是什麼方法傳入進來。

第三個參數:是方法調用時的參數

首先咱們要注意Object.class.equals(method.getDeclaringClass())

經過method.getDeclaringClass()獲取的class就是咱們傳入的class,確定不是object因此不可能去執行if內部的邏輯,也就是說,不會也不能調用原來方法。由於沒有實現類。

本來的動態代理,在代理實例中的invoke最後咱們常常見到的method.invoke();咱們能夠在這個方法執行的先後加入本身的邏輯,而此次mybatis之因此不能調用這個方法,是由於這個接口沒有實現類因此不須要調用,直接走本身的邏輯,而本身的邏輯就是下面兩行代碼。

final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);

首先是cachedMapperMethod這個方法是返回了一個MapperMethod

image

咱們來看看這個類是如何構造的

首先傳入了一個接口mapperInterface

一個方法method

配置文件sqlSession.getConfiguration

image

而後根據這三個參數又建立了一個SqlCommand的類

this.command = new SqlCommand(config, mapperInterface, method);

這個類的構造方法就比較重要了,仔細看

image

image

首先經過接口的名稱+方法名就構成了statementName

而後從配置文件中去尋找這個statementName

而這個東西咱們寫xml就應該意識到就是xml的id

那麼咱們就能夠知道,這裏所作的事情就是將咱們的接口與咱們的xml進行匹配綁定

也就是爲何咱們執行一個對應的接口方法的時候就能夠找到對應xml裏面執行sql的緣由了。

並且這裏最後從配置文件中拿到了MappedStatement以後就從這個類中能夠拿到

name = ms.getId();
type = ms.getSqlCommandType();

也就是運行的名稱也就是ID和sql的類型,增刪改查

image

而後以前除了SqlCommand還有一句

this.method = new MethodSignature(config, mapperInterface, method);

這裏是在幹什麼呢?

進去以後就會發現一大堆的賦值,可是有一點很明顯

this.returnType = method.getReturnType();

也就是說,這裏構造的所謂的MethodSignature叫方法簽名,就是經過傳入的接口調用的方法獲取返回值類型。這裏要記住哦,下面還有用的。

 

而後咱們再來看看前面兩句中的後面一句,執行

image

return mapperMethod.execute(sqlSession, args);

execute中首先就是根據剛纔獲取的類型,看看是哪種的類型的語句

image

咱們這裏是查詢

這裏就用到爲了咱們剛纔所初始化的方法簽名method,經過這裏對返回值類型的判斷,從而就能執行對應的方法了。

若是你的返回值是list那麼就會調用selectList

若是你的返回值是一個那麼就會調用selectOne等等

總之,返回值的不一樣而致使了最後調用的不一樣,也就最終執行了咱們以前所講過的sql語句了。

以上就是爲何咱們經過getMapper方法獲取接口以後,直接調用接口就能夠獲取到返回數據的緣由了。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

總結

一、明確了MyBatis面向接口式編程的所有原理。

二、整個過程有點長,若是對於代理模式和反射掌握的不熟悉的話推薦直接debug模式跟蹤斷點。

三、以後會嘗試寫一個例子來模擬整個過程讓整個過程更加清晰一些。

相關文章
相關標籤/搜索