Mybatis源碼解析-4.插件配置解析

Mybatis插件配置解析

MyBatis 允許你在映射語句執行過程中的某一點進行攔截調用。默認情況下,MyBatis 允許使用插件來攔截的方法調用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

插件的配置通過<plugins>標籤完成,Mybatis文檔中給出的例子如下:

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

XMLConfigBuilder的配置解析邏輯中,pluginElement(XNode parent)方法用於解析插件配置。代碼如下:

private void parseConfiguration(XNode root) {
    ...
    pluginElement(root.evalNode("plugins"));
    ...

}

private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      // 遍歷plugins標籤的所有plugin子標籤
      for (XNode child : parent.getChildren()) {
        String interceptor = child.getStringAttribute("interceptor");
        // 將所有的plugin標籤中聲明的所有property轉化爲Properties對象
        Properties properties = child.getChildrenAsProperties();
        // 實例化配置的plugin
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        // 將plugin需要的屬性初始化到plugin中
        interceptorInstance.setProperties(properties);
        // 將初始化完成的plugin放入到Configuration對象的interceptorChain屬性中
        configuration.addInterceptor(interceptorInstance);
      }
    }
}

可以看到,插件配置被解析時就已經創建了插件的實例對象,而每個插件是按照攔截器模式進行處理業務的,我們這裏可以再次考察兩個問題:

  1. 插件的保存順序如何?
  2. 攔截器接口到底是怎樣的格式?

插件的保存順序

要考察插件的保存順序,我們必須深入到如下代碼中:

configuration.addInterceptor(interceptorInstance);

考察Configuration對象的addInterceptor(Interceptor)方法,可以發現:

public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
}

該方法僅僅爲攔截器鏈添加了一個攔截器罷了,具體的攔截器添加邏輯在InterceptorChain中,那麼攔截器鏈到底是怎樣的結構呢?其實攔截器鏈就是一個鏈表:

public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<>();

}

其添加邏輯更是簡單,完全不用考慮攔截器的執行順序,僅僅是將鏈表中添加一個節點罷了。

public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
}

綜上所述,我們在Mybatis配置文件中配置的所有插件,在配置解析完畢後,會變成一個攔截器,並存儲於Configuration對象的攔截器鏈中,大致邏輯如下圖所示:

【mybatis-config.xml - <plugins>|                       ^
    v                       |
【XMLConfigBuilder.parseConfiguration(parser.evalNode("/configuration")】  // 解析配置文件
    |                   ^
    v                   |
【XMLConfigBuilder.pluginElement(root.evalNode("plugins"))】  // 解析plugins標籤
        |           ^
        v           |
【XMLConfigBuilder.configuration.addInterceptor(interceptorInstance)】// 創建攔截器,並將攔截器放入到configuration對象的攔截器鏈中
            |   ^
            v   |
【interceptorChain.addInterceptor(interceptor);

攔截器接口格式

通過上一部分我們知道,Mybatis的插件最後都會被作攔截器處理,這裏我們考察一下在Mybatis中攔截器接口的格式:

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  default void setProperties(Properties properties) {
    // NOP
  }

}

該攔截器接口共聲明瞭三個方法:

  1. setProperties(Properties properties):通過上一節分析我們知道,該方法是在解析plugin標籤的最後一步,將plugin標籤的子標籤中聲明的所有properties設置到攔截器實例中。
  2. plugin(Object target):該方法用於聲明通過何種方式實現攔截器模式,Mybatis默認情況下是使用JDK動態代理的方式實現的攔截器模式,你可以覆蓋該方法對其進行更改,之後介紹到攔截器具體執行步驟時,會對比攔截器模式這樣實現的好處與缺點
  3. intercept(Invocation):該方法是攔截器執行的真正業務邏輯。

如此看來,Mybatis的攔截器接口聲明的三個方法分別用於:初始化、構建以及真正的業務操作。將三者解耦開來,是一種蠻不錯設計。

至此,我們已經分析完,Mybatis插件初始化過程,至於更多關於插件執行的細節,我們會在後面進行更深入的分析。不過現在先讓我們把Mybatis的配置解析分析完,接下來讓我們去看一下最重要的<mappers>標籤的解析。

下一節:Mybatis源碼解析-5.Mappers標籤解析

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