一、基本概要
參見mybatis官方文檔:點擊打開鏈接
1. 快速開發;
2. 插件原理實現的主要技術和思想;
3. 爲什麼插件只支持Executor、StatementHandler、ResultSetHandler、ParameterHandler四種類型?
二、快速開發
mybatis插件開發極易上手,參見第一節中mybatis官方文檔中的demo,只需要明白幾個概念即可快速開發。
1. 官方示例
@Intercepts({ @Signature(type = Executor.class, method = "update", args = {
MappedStatement.class, Object.class }) })
public class ExamplePlugin implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
return invocation.proceed();
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
}
}
2. 快速開發步驟
1. 實現Interceptor接口;
2. @Interceptor註解中的注意事項:
a. type是Executor、StatementHandler、ResultSetHandler、ParameterHandler中的一個或多個;
b. method爲上述四個類型中定義的方法名;
c. args爲b中需要攔截的method的參數類型;
3. 最後,在mybatis-config.xml中聲明自定義開發的插件即可;
<plugins>
<plugin interceptor="xx.xx.Xxxx" />
</plugins>
3. 注意事項
mybatis插件開發會存在重複代理的情況,因此在做插件開發的時候,最好在Interceptor.plugin方法中使用Class.isAssignableFrom(Class)來進行判斷,避免重複代理問題。
至於爲什麼會產生重複代理,後續源碼閱讀時候會進一步分析。
三、插件支持類型
如上文所述,mybatis插件開發只支持四種類型,Executor、StatementHandler、ResultSetHandler、ParameterHandler。之所以mybatis只支持上述四種類型,其關鍵在於一句源碼:interceptorChain.pluginAll(executor)。以Executor源碼爲例,簡要分析爲什麼mybatis只支持這四種類型。
1. 思想及源碼
1. 當new SqlSessionFactoryBuilder().builder(InputStream)的時候,mybatis會將mybatis-config.xml配置文件中的內容解析成Configuration對象,以便於運行時獲取;
2. 此時所有<plugins>標籤中plugin會被實例化並註冊到Configuration的屬性InterceptorChain中,在InterceptorChain中實際上是一個ArrayList來保存攔截器;
3. 當Mybatis啓動完成後,SqlSessionFactory.openSession(),此時會通過配置中心Configuration實例化相應的Executor,在實例化的同時完成相應代理的生成;
4. 上述步驟中,interceptor.plugin(Object target)實際上是需要我們來實現Interceptor接口的,在實現接口的plugin方法時,target爲目標代理類,即四種類型之一,而plugin中的邏輯主要是通過Plugin.warp(target, interceptor)來完成,最終生成相應的JDK動態代理;
3. 其他三種類型概要
至於其他三種類型,同理也是根據Mybatis配置中心生成相應對象的時候調用了interceptorChain.pluginAll方法。因此,Mybatis框架之所以只支持上述四種類型,其原因是框架中只有這四類對象生成的時候會條用該源碼。
四、插件實現技術及思想
1. 思想
如上文分析中所設計,實際上mybatis的插件技術基於JDK動態代理,但是在代理生成的過程中並不是對所有的方法都進行代理,因此這一部分的思想又體現成爲:
1. 不需要代理的方法,直接反射調用metho.invoke(target, args);
2. 需要進行代理的方法,回調到攔截器Interceptor所提供的上下文場景中進行代理邏輯調用,並反射調用原方法邏輯;
3. 爲了將代理邏輯整合到插件中,Mybatis提供了Interceptor.intercept來提供代理邏輯環境,通過Invocation類來分離代理目標的原有邏輯,並在Plugin.invoke方法中對以上兩種邏輯進行解耦和;
2. 源碼分析
承接上文分析,當Mybatis啓動完成之後,插件對象相應的JDK動態代理對象也隨之生成,此時調用代理的方法,將會轉到InvocationHandler的invoke方法中執行,而在Mybatis插件源碼中,實現InvocationHandler接口的正是Plugin類。
此時調用代理目標對象將會進入invoke方法中執行。
經過以上解耦和後,轉到需要我們實現的Interceptor接口中執行代理邏輯和元邏輯。
五、再議重複代理
實際上第一次看源碼的時候,我發現如果有多個代理,則會重複的調用Interceptor.plugin方法,因此懷疑因此會導致重複代理,即目標對象的代理對象再次進行代理,有幾個插件實例,則會進行重複代理幾次。
但是實際上是不會導致重複代理的,但是會導致有些不必要的方法重複調用,因此最好在Interceptor的plugin方法中加入2.3部分判斷邏輯。
如圖所示,在進行代理生成之前,需要獲取代理對象實現的接口,在該方法的邏輯中實際上是通過target.class.getInterfaces,因此不同的target得到的實現接口是不一樣的,因此不會互導致重複代理問題。
附註:
本文寫的倉促,如有錯漏,煩請不吝指正,謝謝!