關於Mybatis插件,大部分人都知道,也都使用過,但不少時候,咱們僅僅是停留在表面上,知道Mybatis插件能夠在DAO層進行攔截,如打印執行的SQL語句日誌,作一些權限控制,分頁等功能;但對其內部實現機制,涉及的軟件設計模式,編程思想每每沒有深刻的理解。編程
本篇案例將幫助讀者對Mybatis插件的使用場景,實現機制,以及其中涉及的編程思想進行一個小結,但願對之後的編程開發工做有所幫助。設計模式
注:本案例以mybatis 3.4.7-SNAPSHOT版本爲例。mybatis
PS:文章是挺久以前寫的,當時花了一些心思,存到電腦的word裏,今天正好看到,就是裏面的源碼都是圖片,哈哈哈,湊合着看吧。性能
mybatis的分頁默認是基於內存分頁的(查出全部,再截取),數據量大的狀況下效率較低,不過使用mybatis插件能夠改變該行爲,只須要攔截StatementHandler類的prepare方法,改變要執行的SQL語句爲分頁語句便可;單元測試
通常業務系統都會有建立者,建立時間,修改者,修改時間四個字段,對於這四個字段的賦值,實際上能夠在DAO層統一攔截處理,能夠用mybatis插件攔截Executor類的update方法,對相關參數進行統一賦值便可;測試
對於SQL語句執行的性能監控,能夠經過攔截Executor類的update, query等方法,用日誌記錄每一個方法執行的時間;ui
其實mybatis擴展性仍是很強的,基於插件機制,基本上能夠控制SQL執行的各個階段,如執行階段,參數處理階段,語法構建階段,結果集處理階段,具體能夠根據項目業務來實現對應業務邏輯。插件
與其稱爲Mybatis插件,不如叫Mybatis攔截器,更加符合其功能定位,實際上它就是一個攔截器,應用代理模式,在方法級別上進行攔截。設計
那麼這些類上的方法都是在什麼階段被攔截的呢?爲理解這個問題,咱們先看段簡單的代碼(摘自mybatis源碼中的單元測試SqlSessionTest類),來了解下典型的mybatis執行流程,以下代碼所示:3d
以上代碼主要完成如下功能:
以下是時序圖,在整個時序圖中,涉及到mybatis插件部分已標紅,基本上就是體如今上文中提到的四個類上,對這些類上的方法進行攔截。
先來看下mybatis是如何加載插件配置的,對應的xml配置信息以下:
對應的解析代碼以下,主要作如下工做:
以上邏輯對應的時序圖以下:
Mybatis插件的實現機制主要是基於動態代理實現的,其中最爲關鍵的就是代理對象的生成,因此有必要來了解下這些代理對象是如何生成的。
Executor代理對象
ParameterHandler代理對象
ResultSetHandler代理對象
StatementHandler代理對象
觀察源碼,發現這些可攔截的類對應的對象生成都是經過InterceptorChain的pluginAll方法來建立的,進一步觀察pluginAll方法,以下:
遍歷全部攔截器,調用攔截器的plugin方法生成代理對象,注意生成代理對象從新賦值給target,因此若是有多個攔截器的話,生成的代理對象會被另外一個代理對象代理,從而造成一個代理鏈條,執行的時候,依次執行全部攔截器的攔截邏輯代碼;
接下來看一下咱們在編寫攔截器的時候,一個典型的plugin方法實現方式,以下:
再進一步查看wrap方法,以下:
典型的動態代理實現,調用的是Proxy.newProxyInstance方法來生成代理對象。
以上邏輯對應的時序圖以下,這裏咱們假設聲明瞭兩個攔截器,那麼在建立target代理對象的時候,最終返回的代理對象proxy2,實際上代理了proxy1,而proxy1又代理了target,:
因爲真正去執行Executor、ParameterHandler、ResultSetHandler和StatementHandler類中的方法的對象是代理對象(建議將代理對象轉爲class文件,反編譯查看其結構,幫助理解),因此在執行方法時,首先調用的是Plugin類(實現了InvocationHandler接口)的invoke方法,以下:
首先根據執行方法所屬類獲取攔截器中聲明須要攔截的方法集合;
判斷當前方法需不須要執行攔截邏輯,須要的話,執行攔截邏輯方法(即Interceptor接口的intercept方法實現),不須要則直接執行原方法。
能夠關注下Interceptor接口的intercept方法實現,通常須要用戶自定義實現邏輯,其中有一個重要參數,即Invocation類,經過改參數咱們能夠獲取執行對象,執行方法,以及執行方法上的參數,從而進行各類業務邏輯實現,通常在該方法的最後一句代碼都是invocation.proceed()(內部執行method.invoke方法),不然將沒法執行下一個攔截器的intercept方法。
以上邏輯對應的時序圖以下,這裏咱們以執行executor對象的query方法爲例,且假設有兩個攔截器存在:
這裏以分頁插件爲例,來了解下通常mybatis插件的編寫規則,以下所示:
主要須要實現三個方法
簡單的說,mybatis插件就是對ParameterHandler、ResultSetHandler、StatementHandler、Executor這四個接口上的方法進行攔截,利用JDK動態代理機制,爲這些接口的實現類建立代理對象,在執行方法時,先去執行代理對象的方法,從而執行本身編寫的攔截邏輯,因此真正要用好mybatis插件,主要仍是要熟悉這四個接口的方法以及這些方法上的參數的含義;
另外,若是配置了多個攔截器的話,會出現層層代理的狀況,即代理對象代理了另一個代理對象,造成一個代理鏈條,執行的時候,也是層層執行;
關於mybatis插件涉及到的設計模式和軟件思想以下:
一些注意事項: