1. 程式人生 > >mybatis介面程式設計原理解析二

mybatis介面程式設計原理解析二

一、引言

前面一篇文章介紹了mybatis之所以推薦採用介面式程式設計而不是採用SqlSession的方法的原因,也引出了介面式程式設計之所以能實現這背後的大boss:反射與動態代理。在這篇文章中就不再贅述,詳情請看https://blog.csdn.net/Wenlong_L/article/details/82942831

下面開始實際探究mybatis介面式程式設計背後的祕密。

二、整體把控

我們說介面如果沒有實際的實現類是不能直接呼叫的,但是我們確實呼叫了介面的方法,這裡是採用的是jdk的動態代理實現的,有關jdk動態代理的知識我不詳細解釋,可以參見我前面的部落格:https://blog.csdn.net/Wenlong_L/article/details/82843481

。這是java程式設計基礎裡面就應該學習的。這裡的代理類為MapperProxy,參照下面這幅圖來講解,當執行介面的方法queryMessageList()方法時,實際上執行的是MapperProxy這個代理類的invoke()方法。

既然最終呼叫的是invoke方法,對於常規的動態代理來說,我們一般在invoke方法中對被代理類的方法進行增強,也就是說一般先執行before()方法,再執行被代理類物件的方法,最後執行after()方法。其中before()與after()這兩個方法就是對被代理物件方法進行的增強呼叫。但是在這裡我們不能執行被代理物件的方法,原因很簡單,我們這裡代理的是一個介面的class物件,並不是代理的一個實現了介面的類。如下圖所示,在這裡invoke()方法中最終是通過呼叫SqlSession的方法執行SQL語句的,那代理這個介面的意義何在呢?主要是介面全限定名class.getName()+介面方法名是和mapper配置檔案中的namespace+id相同的,這樣就可以知道給SqlSession中的方法的第一個引數SQL的id傳參。

所以最終可以得出如下圖的結論,最終回到了最初的呼叫形式上,這樣曲折來回主要就是為了規避以前直接呼叫SqlSession方法的風險,mybatis框架的設計者自己解決了這種隱藏的風險,不讓其暴露給應用開發者。至於最終對SQL語句的執行實質上是一樣的,最終都回到了SqlSesseion中的方法上。

三、關鍵細節程式碼分析

類:DefaultSqlSession

類:Configuration

類:MapperRegistry

從上面的過程可以看出,SqlSession的getMapper()方法首先是從Configuration物件中取出MapperRegistry物件,再從MapperRegistry物件中取出MapperProxyFactory物件。這些物件都是在mybatis讀取配置檔案時封裝的,主要是通過addMapper方法,感興趣的可以去看一下mybatis讀取配置檔案封裝Configuration物件,這裡不詳細說明。需要注意這裡使用了一個工廠方法,MapperRegistry通過addMapper方法注入給自己的並不是直接的MapperProxy物件,而是一個MapperProxyFactory工廠物件。通過該工廠物件的newInstance()方法構造MapperProxy物件。下面看一下構造過程:

類:MapperProxyFactory

類:MapperProxyFactory

上面紅色圈出來的我們一定非常熟悉吧,這就是生成代理類例項的方法呼叫,第一個引數是被代理的class物件的classLoader,第二個引數是被代理的class物件陣列,第三個引數是代理類例項。所以下面我們主要是看代理類例項的invoke()方法:

類:MapperProxy

上面三種顏色圈出來的部分都有值得分析的細節。

紅色方框中的程式碼:

有人可能會奇怪為什麼要有這麼一個判斷,在我們常規的動態代理invoke()方法中,會執行被代理物件的方法,通過method.invoke()反射的方式,但是這裡我們代理的是沒有具體實現類的介面,我們是不能執行介面方法的,所以這裡會判斷method所屬的類是否是Object,如果不是則這段程式碼不會被執行,顯然我們執行的是介面的方法,自然不會是Object物件的方法了,又有人會問,那既然會跳過這段程式碼,為什麼不乾脆去掉這段程式碼呢?這裡就很微妙了,我們生成的代理類物件不僅可以代理執行被代理類的方法,它自身也是一個類物件,所以不能忘了它也具有從超類Object類繼承的方法(如toString、hashCode、getClass等),當執行這些方法時,他們都是Object物件的方法,所以會進入if條件語句執行。也就是說我們invoke執行的是被代理介面的方法時會跳過if,最終是通過紫色方框中的程式碼執行的;而我們invoke如果執行的是Object物件(toString等)方法時直接進if中採用反射執行,不會執行紫色方框中的程式碼。

紫色方框中的程式碼:

我們說了介面方法不能直接執行,所以代理介面方法時實際上執行的是紫色方框中的方法。主要是通過mapperMethod的execute方法執行,這個方法我們稍後會進去跟蹤一下原始碼。

黃色方框中的程式碼:

這裡面的程式碼是從MethodCache中取MapperMethod物件,如果沒有則構造一個MapperMethod物件並將其存在MethodCache中以便以後使用,這裡是一個典型的享元模式,減少系統中物件的個數,提高效能。

類:MapperMethod

上面是MapperMethod的execute方法具體的執行,通過SqlCommand的type屬性來執行相應的增刪改查,從紅色方框可以看到我們早就想看到的程式碼了,是不是很熟悉。而SqlCommand物件有兩個比較重要的屬性:name、type:

類:SqlCommand

上面紫色方框中的statementName的封裝有沒有很熟悉,採用的是介面的全限定名.介面方法名的方式,這就是我們在mapper配置檔案中的的namespace+id,這兩個類的兩個屬性name就是SQL的id,type就是根據標籤insert、select、update、delete。這就是execute方法中執行哪個分支的依據。

四、總結

這篇文章主要介紹了mybatis介面程式設計的原理,過程可能顯得比較雜亂,下一篇文章中我將模擬mybatis介面程式設計原理寫一個模擬的動態代理實現,敬請期待!