MyBatis源碼解讀(3)——MapperMethod


在前面兩篇的MyBatis源碼解讀中,我們一路跟蹤到了MapperProxy,知道了盡管是使用了動態代理技術使得我們能直接使用接口方法。為鞏固加深動態代理,我們不妨再來回憶一遍何為動態代理。

我相信在初學MyBatis的時候幾乎每個人都會發出一個疑問,為什么明明是XXXDao接口,我沒有用任何代碼實現這個接口,但卻能直接使用這個接口的方法。現在清楚了,動態代理。我們來寫一個demo小程序來看看。

首先是一個Test.java的接口,只有一個say方法。

 1 package day_16_proxy;
 2 
 3 /**
 4  * @author 余林豐
 5  *
 6  * 2016年11月16日
 7  */
 8 public interface Test {
 9     void say();
10 }

我們現在想像MyBatis那樣不用實現它而是直接調用。

test.say();

當然不可能直接實例化一個接口,此時就需要生成一個代理類TestProxy.java,這個類需實現InvocationHandler接口。

 1 package day_16_proxy;
 2 
 3 import java.lang.reflect.InvocationHandler;
 4 import java.lang.reflect.Method;
 5 
 6 /**
 7  * @author 余林豐
 8  *
 9  * 2016年11月16日
10  */
11 public class TestProxy implements InvocationHandler {
12 
13     /* (non-Javadoc)
14      * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
15      */
16     @Override
17     public Object invoke(Object proxy, Method method, Object[] args)
18             throws Throwable {
19         if (method.getName().equals("say")){
20             System.out.println("hello world");
21         }
22         return null;
23     }
24 
25 }

需要實現invoke方法,意思就是“調用”的意思(當然我們想要調用接口中的方法時並不會顯示調用invoke)。我們從第19行看到,當調用的方法是say時,輸出“hello world”。有了這個TestProxy.java代理類過后,我們再來客戶端代碼中測試。

 1 package day_16_proxy;
 2 
 3 import java.lang.reflect.Proxy;
 4 
 5 /**
 6  * @author 余林豐
 7  *
 8  * 2016年11月16日
 9  */
10 public class Client {
11 
12     public static void main(String[] args){
13         Test test = (Test)Proxy.newProxyInstance(Test.class.getClassLoader(), new Class<?>[]{Test.class}, new TestProxy());
14         test.say();
15     }
16 }

在第14行代碼中,我們已經能夠直接調用Test接口中的say方法了,原因就在於我們通過Proxy.newProxyInstance方法生成了一個代理類實例即TestProxy。

回到我們的MyBatis源碼,在上一節中我們知道了一個Dao接口實際上是通過MapperProxyFactory生成了一個MapperProxy代理類。

1 //org.apache.ibatis.binding.MapperProxyFactory
2 protected T newInstance(MapperProxy<T> mapperProxy) {
3   return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
4 }

第3行代碼是不是和在開頭的例子中客戶端測試代碼如出一轍?生成代理類,這個代理類就是MapperProxy。清楚了MyBatis是如何構造出代理類的算是解決了第一個問題——一個接口怎么能直接調用其方法。

現在拋出第二個問題——接口中每個具體的方法是如何做到一一實現代理的呢?我們再來看看MapperProxy類。這次我們先看MapperProxy類實現的InvocationHanlder.invoke方法。

 1 //org.apache.ibatis.binding.MapperProxy
 2 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 3   if (Object.class.equals(method.getDeclaringClass())) {
 4     try {
 5       return method.invoke(this, args);
 6     } catch (Throwable t) {
 7       throw ExceptionUtil.unwrapThrowable(t);
 8     }
 9   }
10   final MapperMethod mapperMethod = cachedMapperMethod(method);
11   return mapperMethod.execute(sqlSession, args);
12 }

第3行做一個判斷,判斷是否是一個類,如果是一個類,那么久直接傳遞方法和參數調用即可。但我們知道此時是一個接口(也可以自己實現接口,舊版本通常這樣做)。如果不是一個類的話,就會創建一個MapperMethod方法。見名思意:好像就是這個類在執行我們所調用的每一個接口方法。最后返回的是MapperMethod.execute方法。暫時不予理會MapperProxy類中的cachedMapperMethod方法。

來看看MapperMethod類,這個MapperMethod類就不得了了啊,可以說它是統管所有和數據庫打交道的方法(當然概括起來也只有insert、delete、update、select四個方法)。所以,不管你的dao層有多少方法,歸結起來的sql語句都有且僅有只有insert、delete、update、select,可以預料在MapperMethod的execute方法中首先判斷是何種sql語句。

//org.apache.ibatis.binding.MapperMethod
public Object execute(SqlSession sqlSession, Object[] args) {
    switch (command.getType()) {
        case INSERT: {……}
        case UPDATE: {……}
        case DELETE: {……}
        case SELECT: {……}
        case FLUSH: {……}
    }
}

這是MepperMethod.execute方法的刪減,我們可以看到確實在execute方法內部首先判斷是何種sql語句。(注意:在閱讀這部分源代碼時,我們的主線是MyBatis是如何創建出一個代理類,以及實現其方法的,而暫時忽略其中的細節)

我們選擇常見的"SELECT"sql語句來進行解讀,而在"SELECT"語句中又會設計到較多的細節問題:

 1 //org.apache.ibatis.binding
 2 case SELECT:
 3         if (method.returnsVoid() && method.hasResultHandler()) {
 4           executeWithResultHandler(sqlSession, args);
 5           result = null;
 6         } else if (method.returnsMany()) {
 7           result = executeForMany(sqlSession, args);
 8         } else if (method.returnsMap()) {
 9           result = executeForMap(sqlSession, args);
10         } else if (method.returnsCursor()) {
11           result = executeForCursor(sqlSession, args);
12         } else {
13           Object param = method.convertArgsToSqlCommandParam(args);
14           result = sqlSession.selectOne(command.getName(), param);
15         }
16         break;

我們選取第7行中的executeForMany中的方法來解讀試試看。

 1 //org.apache.ibatis.binding.MapperMethod
 2 private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
 3   List<E> result;
 4   Object param = method.convertArgsToSqlCommandParam(args);
 5   if (method.hasRowBounds()) {
 6     RowBounds rowBounds = method.extractRowBounds(args);
 7     result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
 8   } else {
 9     result = sqlSession.<E>selectList(command.getName(), param);
10   }
11   // issue #510 Collections & arrays support
12   if (!method.getReturnType().isAssignableFrom(result.getClass())) {
13     if (method.getReturnType().isArray()) {
14       return convertToArray(result);
15     } else {
16       return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
17     }
18   }
19   return result;
20 }

第7行和第9行代碼就是我們真正執行sql語句的地方,原來兜兜轉轉它又回到了sqlSession的方法中。在下一節,我們再重新回到重要的SqlSession中。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM