標籤:但我 nbsp 自己實現 erp date catch dem rtt stp
在前面兩篇的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 @Override17 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.MapperProxyFactory2 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.MapperMethodpublic 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 support12 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中。
MyBatis源碼解讀(3)——MapperMethod