【Mybatis】mybatis插件源碼分析

一、基本概要

    參見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得到的實現接口是不一樣的,因此不會互導致重複代理問題。


附註:

    本文寫的倉促,如有錯漏,煩請不吝指正,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章