天天看點

MyBatis 源碼分析 - 插件機制

一般情況下,開源架構都會提供插件或其他形式的拓展點,供開發者自行拓展。這樣的好處是顯而易見的,一是增加了架構的靈活性。二是開發者可以結合實際需求,對架構進行拓展,使其能夠更好的工作。以 MyBatis 為例,我們可基于 MyBatis 插件機制實作分頁、分表,監控等功能。由于插件和業務無關,業務也無法感覺插件的存在。是以可以無感植入插件,在無形中增強功能。

開發 MyBatis 插件需要對 MyBatis 比較深了解才行,一般來說最好能夠掌握 MyBatis 的源碼,門檻相對較高。本篇文章在分析完 MyBatis 插件機制後,會手寫一個簡單的分頁插件,以幫助大家更好的掌握 MyBatis 插件的編寫。

我們在編寫插件時,除了需要讓插件類實作 Interceptor 接口,還需要通過注解标注該插件的攔截點。所謂攔截點指的是插件所能攔截的方法,MyBatis 所允許攔截的方法如下:

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

ParameterHandler (getParameterObject, setParameters)

ResultSetHandler (handleResultSets, handleOutputParameters)

StatementHandler (prepare, parameterize, batch, update, query)

如果我們想要攔截 Executor 的 query 方法,那麼可以這樣定義插件。

除此之外,我們還需将插件配置到相關檔案中。這樣 MyBatis 在啟動時可以加載插件,并儲存插件執行個體到相關對象(InterceptorChain,攔截器鍊)中。待準備工作做完後,MyBatis 處于就緒狀态。我們在執行 SQL 時,需要先通過 DefaultSqlSessionFactory 建立 SqlSession 。Executor 執行個體會在建立 SqlSession 的過程中被建立,Executor 執行個體建立完畢後,MyBatis 會通過 JDK 動态代理為執行個體生成代理類。這樣,插件邏輯即可在 Executor 相關方法被調用前執行。

以上就是 MyBatis 插件機制的基本原理。接下來,我們來看一下原理背後對應的源碼是怎樣的。

本節,我将以 Executor 為例,分析 MyBatis 是如何為 Executor 執行個體植入插件邏輯的。Executor 執行個體是在開啟 SqlSession 時被建立的,是以,下面我們從源頭進行分析。先來看一下 SqlSession 開啟的過程。

Executor 的建立過程封裝在 Configuration 中,我們跟進去看看看。

如上,newExecutor 方法在建立好 Executor 執行個體後,緊接着通過攔截器鍊 interceptorChain 為 Executor 執行個體植入代理邏輯。那下面我們看一下 InterceptorChain 的代碼是怎樣的。

以上是 InterceptorChain 的全部代碼,比較簡單。它的 pluginAll 方法會調用具體插件的 plugin 方法植入相應的插件邏輯。如果有多個插件,則會多次調用 plugin 方法,最終生成一個層層嵌套的代理類。形如下面:

MyBatis 源碼分析 - 插件機制

當 Executor 的某個方法被調用的時候,插件邏輯會先行執行。執行順序由外而内,比如上圖的執行順序為 <code>plugin3 → plugin2 → Plugin1 → Executor</code>。

plugin 方法是由具體的插件類實作,不過該方法代碼一般比較固定,是以下面找個示例分析一下。

如上,plugin 方法在内部調用了 Plugin 類的 wrap 方法,用于為目标對象生成代理。Plugin 類實作了 InvocationHandler 接口,是以它可以作為參數傳給 Proxy 的 newProxyInstance 方法。

到這裡,關于插件植入的邏輯就分析完了。接下來,我們來看看插件邏輯是怎樣執行的。

Plugin 實作了 InvocationHandler 接口,是以它的 invoke 方法會攔截所有的方法調用。invoke 方法會對所攔截的方法進行檢測,以決定是否執行插件邏輯。該方法的邏輯如下:

invoke 方法的代碼比較少,邏輯不難了解。首先,invoke 方法會檢測被攔截方法是否配置在插件的 @Signature 注解中,若是,則執行插件邏輯,否則執行被攔截方法。插件邏輯封裝在 intercept 中,該方法的參數類型為 Invocation。Invocation 主要用于存儲目标類,方法以及方法參數清單。下面簡單看一下該類的定義。

關于插件的執行邏輯就分析到這,整個過程不難了解,大家簡單看看即可。

為了更好的向大家介紹 MyBatis 的插件機制,下面我将手寫一個針對 MySQL 的分頁插件。Talk is cheap. Show code.

上面的分頁插件通過 RowBounds 參數擷取分頁資訊,并生成相應的 limit 語句。之後拼接 sql,并使用該 sql 作為參數建立 StaticSqlSource。最後通過反射替換 MappedStatement 對象中的 sqlSource 字段。以上代碼中出現了一些大家不太熟悉的類,比如 BoundSql,MappedStatement 以及 StaticSqlSource,這裡簡單解釋一下吧。BoundSql 包含了經過解析後的 sql 語句,以及使用者運作時傳入的參數,這些參數最終會被設定到 sql 中。MappedStatement 與映射檔案中的 &lt;select&gt;,&lt;insert&gt; 等節點對應,包含了節點的配置資訊,比如 id,fetchSize 以及 SqlSource。StaticSqlSource 是 SqlSource 實作類之一,包含完全解析後的 sql 語句。所謂完全解析是指 sql 語句中不包含 ${xxx} 或 #{xxx} 等占位符,以及其他一些未解析的動态節點,比如 &lt;if&gt;,&lt;where&gt; 等。關于這些類就介紹這麼多,如果大家還是不怎麼了解的話,可以看看我之前寫的文章。接下裡,寫點測試代碼驗證一下插件是否可以正常運作。先來看一下 Dao 接口與映射檔案的定義:

測試代碼如下:

上面代碼運作之後,會列印如下日志。

MyBatis 源碼分析 - 插件機制

在上面的輸出中,SQL 語句中包含了 LIMIT 字樣,這說明插件生效了。

到此,關于 MyBatis 插件機制就分析完了。總體來說,MyBatis 插件機制比較簡單。但實作一個插件卻較為複雜,需要對 MyBatis 比較了解才行。是以,若想寫出高效的插件,還需深入學習源碼才行。

好了,本篇文章就先到這了。感謝大家的閱讀。

更新時間

标題

2018-09-11

MyBatis 源碼分析系列文章合集

2018-07-16

MyBatis 源碼分析系列文章導讀

2018-07-20

MyBatis 源碼分析 - 配置檔案解析過程

2018-07-30

MyBatis 源碼分析 - 映射檔案解析過程

2018-08-17

MyBatis 源碼分析 - SQL 的執行過程

2018-08-19

MyBatis 源碼分析 - 内置資料源

2018-08-25

MyBatis 源碼分析 - 緩存原理

2018-08-26

MyBatis 源碼分析 - 插件機制

本文在知識共享許可協定 4.0 下釋出,轉載需在明顯位置處注明出處 作者:田小波 本文同步釋出在我的個人部落格:http://www.tianxiaobo.com
MyBatis 源碼分析 - 插件機制

本作品采用知識共享署名-非商業性使用-禁止演繹 4.0 國際許可協定進行許可。