Mybatis源碼之美:2.7.解析plugins元素,完成mybatis插件的配置

解析plugins元素,完成mybatis插件的配置

> 點擊查看typeAliases元素的用法

Mybtis的插件機制是一個很強大的功能,它允許我們在Mybatis運行期間切入到Mybatis內部執行我們想要做的一些事情。

mybatis比較火的分頁插件page-helper其實就是基於Mybatis的插件功能實現的。

Mybatis的pluginsDTD定義如下:

<!--ELEMENT plugins (plugin+)-->

<!--ELEMENT plugin (property*)-->a
<!--ATTLIST plugin
interceptor CDATA #REQUIRED
-->

plugins標籤下必須定義一個或多個plugin子節點。

plugin子節點有一個必填的屬性interceptor ,該屬性指向一個實現了Interceptor的類,同時在plugin標籤下面允許出現零個或多個property標籤,用來配置該插件運行時依賴的屬性。

比如:

<!-- mybatis-config.xml -->
<plugins>
  <plugin interceptor="org.mybatis.example.ExamplePlugin">
    <property name="someProperty" value="100" />
  </plugin>
</plugins>

Interceptor是mybatis提供的插件接口定義:

/**
 * Mybatis 攔截器(插件)接口定義
 *
 * @author Clinton Begin
 */
public interface Interceptor {
    /**
     * 提供被攔截方法的代理實現,完成額外的業務處理
     *
     * @param invocation 代理
     */
    Object intercept(Invocation invocation) throws Throwable;

    /**
     * 該方法用於處理被攔截對象,返回該對象本身或者爲其生成一個代理對象。
     * <p>
     * 如果返回的是代理對象,我們可以爲指定方法實現代理實現:{@link #intercept(Invocation)}
     *
     * @param target 被攔截的對象
     */
    Object plugin(Object target);

    /**
     * 配置該插件運行時依賴的屬性
     */
    void setProperties(Properties properties);

}

它定義了三個方法:

  • intercept方法用於提供被攔截方法的代理實現,完成額外的業務處理。
  • plugin方法負責處理被攔截的對象,返回該對象本身或者爲其生成一個代理對象。
  • setProperties方法用於配置該插件運行時依賴的屬性。

其中intercept方法的入參是一個Invocation類型的對象,Invocation對象是mybatis提供的一個方法反射操作封裝對象,他維護了特定的方法對象及其運行時依賴的參數。

Invocation定義了三個屬性:

/**
 * 被代理的對象
 */
private final Object target;
/**
 * 被代理的方法
 */
private final Method method;
/**
 * 被代理方法的入參
 */
private final Object[] args;

這三個屬性的賦值操作是在Invocation的構造方法中完成的:

public Invocation(Object target, Method method, Object[] args) {
     this.target = target;
     this.method = method;
     this.args = args;
 }

同時Invocation不僅對外提供了這三個屬性的getter方法,還提供了一個用於完成方法反射操作的proceed方法:

/**
 * 通過反射完成方法調用
 *
 * @return 方法返回值
 */
public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
}

補充完基礎知識之後,我們回到plugins元素的解析操作上來:

// 插件配置
pluginElement(root.evalNode("plugins"));

pluginElement方法中,XmlConfigBuilder會依次處理所有的plugin子節點,獲取其interceptor的屬性值,交給BaseBuilderresolveClass(String)方法獲取具體的插件實現類,

並讀取plugin子節點下的所有property屬性配置生成Properties對象。

/**
 * 解析plugins節點
 *
 * @param parent plugins節點
 */
private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            // 處理每一個interceptor
            String interceptor = child.getStringAttribute("interceptor");
            // 獲取運行時屬性配置
            Properties properties = child.getChildrenAsProperties();
            // 獲取插件的實現
            Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
            // 初始化插件配置
            interceptorInstance.setProperties(properties);
            // 註冊插件,在插件職責鏈中註冊一個新的插件實例
            configuration.addInterceptor(interceptorInstance);
        }
    }
}

> 因爲這裏用的是resolveClass方法來獲取插件的實現類,所以對於插件的配置,也是支持別名機制的。

之後通過反射得到具體的插件對象實例,並調用其setProperties方法配置其運行時需要使用的屬性。

完成上訴操作之後,調用configurationaddInterceptor(Interceptor)方法完成保存插件的工作。

/**
 * 添加一個攔截器
 * @param interceptor 攔截器
 */
public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
}

configurationaddInterceptor(Interceptor)方法中將添加插件的工作交給了interceptorChainaddInterceptor方法來完成。

interceptorChain屬性在Configuration中被硬編碼爲InterceptorChain類型的實例:

/**
 * 插件攔截鏈(Mybatis插件)
 */
protected final InterceptorChain interceptorChain = new InterceptorChain();

InterceptorChain定義了一個List<interceptor>類型的屬性interceptors用於存放mybatis中所有的插件實現類。

/**
 * 所有插件
 */
private final List<interceptor> interceptors = new ArrayList&lt;&gt;();

對外暴露的addInterceptor方法就是用來往interceptors集合中添加Interceptor實現的:

/**
 * 添加一個新的{@link Interceptor#}實例
 */
public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
}

除了addInterceptor方法之外,InterceptorChain還對外暴露了getInterceptorspluginAll兩個方法。

其中getInterceptors用於獲取當前所有Interceptor實例:

/**
 * 獲取所有的Interceptor
 */
public List<interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
}

pluginAll方法則負責依次調用所有的Interceptor實例的plugin方法對目標對象進行處理。

/**
 * 調用所有插件的{@link Interceptor#plugin(Object)}方法,包裝指定對象
 */
public Object pluginAll(Object target) {
    // 多層攔截器對目標對象進行代理,會形成一條職責鏈
    for (Interceptor interceptor : interceptors) {
        // 處理被攔截對象,返回該對象本身或者爲其生成一個代理對象。
        target = interceptor.plugin(target);
    }
    return target;
}

如果多個Interceptor對目標對象進行了代理,那麼多層代理之間就會形成一條職責鏈。

職責鏈模式(責任鏈模式)也是一種常見的行爲型設計模式:

  • 責任鏈模式是由多個對象構成的一條鏈式結構,該結構中的對象之間通過前者持有後者引用的方式貫穿鏈路.
  • 用戶在發起對責任鏈的請求時,並不知道請求會被責任鏈中的哪一個對象處理,這樣做用戶只需要直接對責任鏈請求即可,而不需要針對每一個具體的對象發起請求.

責任鏈模式按照處理請求方式的不同可以分爲純責任鏈和不純的責任鏈:

  • 純責任鏈:

> 在處理請求過程中,每個節點對於請求的處理方式有且僅有處理和不處理兩種,同時請求一定會被責任鏈中的某一個節點處理,當這個節點處理了請求之後,他一定不會將請求繼續往下順延,即,終止請求在鏈路中繼續運行.

  • 不純的責任鏈

> 在不純的責任鏈模式內,一個請求可能會被多個節點處理,即每個節點都可能處理請求的一部分,其後續節點同樣還有可能處理 請求的部分,甚至,還可能存在沒有任何節點處理請求的情形.

理論上講,由interceptors形成的代理對象責任鏈應該是不純的責任鏈。

關注我,一起學習更多知識

關注我

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