1.MyBatis 是什麼
MyBatis 是一個 ORM(Object Relational Mapping,物件 - 關係對映)框架,其中我們寫的 Java 程式碼中的物件資料就是物件模型,關聯式資料庫中的表資料就是關係模型。MyBatis 底層通過 JDBC 介面與關聯式資料庫互動,主要功能是根據對映配置檔案,完成資料在物件模型與關係模型之間的對映,減少了單純用 JDBC 開發時的重複程式碼,只暴露簡單的 API 供開發人員使用。
2.什麼是外掛
外掛是一種常見的擴充套件方式,大多數開源框架也都支援使用者通過新增自定義外掛的方式來 擴充套件或改變框架原有的功能。 Mybatis 中也提供了外掛的功能,雖然叫外掛,但是實際上是通過 攔截器( Interceptor)實現的。在 MyBatis 的外掛模組中涉及 責任鏈模式和 JDK 動態代理的知識, 下文會分析責任鏈模式和 JDK 動態代理在 MyBatis 外掛模組中的實踐。
3.責任鏈模式介紹
責任鏈模式主要用來處理 ”客戶端發出一個請求,有多個物件都有機會來處理這一個請求,但是客戶端不知道究竟誰會來處理他的請求“ 這樣的情況。也就是需要讓請求者和接收者解耦,這樣就可以動態地切換和組合接收者了。 注意,在責任鏈模式中,請求不一定會被處理,因為可能沒有合適的處理者,請求在責任鏈中從頭傳遞到尾,每個處理物件都判斷不屬於自己處理,最後請求就沒有物件來處理。 責任鏈模式的結構圖如下:
當有請求進入時,經過 HandlerOne 的 handlerRequest 方法,再把請求傳遞給 HandlerTwo,處理完再把請求傳遞給 HandlerThree,以此類推,形成一個鏈條。鏈條上每一個物件所承擔的責任各不相同,這就是責任鏈模式。
4.MyBatis 中的攔截器
(1)攔截器的作用點
MyBatis允許使用者使用自定義攔截器對 SQL語句執行過程中的某一點進行攔截。 預設情況 下, MyBatis 允許攔截器攔截 Executor 的方法、 ParameterHandler 的方法、 ResultSetHandler 的 方法 以及 StatementHandler 的方法。這裡說明一下,閱讀過 MyBatis 原始碼的同學應該都知道,這裡的 Executor、ParameterHandler、ResultSetHandler 以及 StatementHandler 都是 MyBatis 操作 SQL 過程中的必備的核心層,而我們的攔截器也就是在這些介面的實現類的方法執行中起作用的。 具體可攔截的方法如下 :
Executor:
update()、query()、flushStatements()、commit()、rollback() 、 getTransaction()、 close()、 isClosed()方法 。
ParameterHandler :
getParameterObject()、 setParameters()方法 。
ResultSetHandler:
handleResultSets()、 handleOutputParameters()方法 。
StatementHandler:
prepare()、 parameterize()、 batch()、 update()、 query()方法。
(2)攔截器定義
public interface Interceptor {
//執行攔截器方法
Object intercept(Invocation invocation) throws Throwable;
// 建立interceptor物件,一般用mybatis提供的Plugin.wrap(...)方法
// 該方法內部會判斷,當前物件是否匹配當前interceptor,匹配則建立代理物件,不匹配則返回當前物件。
Object plugin(Object target);
// mybatis載入初始化時,會根據xml或註解的配置,將攔截器中的配置值取出,呼叫這個方法做初始化操作
void setProperties(Properties properties);
}複製程式碼
我們自定義的攔截器需要實現這個介面,並在 MyBatis 中做好配置,即可生效。
(3)攔截器鏈
public class InterceptorChain {
// 所有已經配置的攔截器
private final List<Interceptor> interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
// mybatis載入初始化時,會根據xml或註解的配置,建立攔截器,呼叫這個方法存入欄位 interceptors 中
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
// 獲取所有的攔截器
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
} 複製程式碼
MyBatis 在啟動載入配置檔案時將已配置的所有攔截器物件(包括自定義的攔截器物件)都放在 InterceptorChain 類物件的欄位 interceptors 中供後續操作使用。 在 InterceptorChain 的 pluginAll 方法中,迴圈 interceptors,呼叫 interceptor.plugin(target) 方法,這個 plugin 方法內部會判斷這個 target 物件是否匹配當前當前的 interceptor 物件,如果匹配則用 JDK 動態代理建立 target 物件的代理物件,否則返回未被代理的 target 物件。這裡是迴圈 interceptors,所以每個 interceptor 都會去執行一次 plugin 方法,也就是上個 interceptor.plugin(target) 呼叫的返回值是下一個 interceptor.plugin(target) 的入參。最後結果就是 target 可能會被代理多次。如下圖:
結果看到了,最後的 target 物件可能是被多次代理過的物件,也就是圖中的 interceptorProxy1 物件。當 interceptorProxy1 執行 interceptor 方法時,內部會呼叫到 interceptorProxy2 物件的 interceptor 方法, interceptorProxy2 的 interceptor 方法內部繼續往下調直到最終的 target 物件呼叫。 說到這,可以看出 target 物件先是匹配所有適合自己的 interceptor 物件,然後在所有匹配的 interceptor 物件中流轉。這就可以理解成責任鏈模式的設計。
5.總結
關於 MyBatis 中攔截器的作用點,攔截器定義,以及攔截器的責任鏈模式在這裡做了一定程度的講解,下篇文章會補充攔截器的代理物件是如何建立的,也就是 interceptor.plugin(target); 代理的內部邏輯實現。