mybatis源碼配置文件解析之四:解析plugins標簽


在前邊的博客在分析了mybatis解析typeAliases標簽,《mybatis源碼配置文件解析之三:解析typeAliases標簽》。下面來看解析plugins標簽的過程。

一、概述

在mybatis的核心配置文件(mybatis-config.xml)文件中,有關plugins的配置如下,

<!-- 攔截器 -->
    <plugins>
        <plugin interceptor="cn.com.mybatis.plugins.MyInterceptor" />
    </plugins>

在mybatis的plugins叫做插件,其實也可以理解為攔截器。在plugins標簽中配置plugin子標簽,plugin子標簽可以配置多個,多個子標簽是有順序的。除了上面的配置方式在每個plugin下還可以配置property子標簽,

<!-- 攔截器 -->
    <plugins>
        <plugin interceptor="cn.com.mybatis.plugins.MyInterceptor" />
        <plugin interceptor="cn.com.mybatis.plugins.MyInterceptor2">
        <property name="name" value="mybatis"/>
        <property name="age" value="10"/>
        </plugin>
    </plugins>

上面的配置在第二個攔截器中新增了兩個屬性name和age,這兩個屬性會被解析為Properties對象。

在上面可以看到一個重要的配置是plugin標簽中的interceptor屬性,該屬性配置的是一個攔截器類,對應該類有什么要求那,一個普通的類就可以嗎,答案是就是一個普通的類,但必須滿足下面的幾個條件,才可以被mybatis作為插件使用,

  • 實現mybatis中的Interceptor接口,並重寫其中的三個方法;
  • 在類上聲明@Intercepts注解,該注解標明該攔截器攔截的方法,一個例子如下,
@Intercepts({@Signature(type=Executor.class,method="query",args= {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})

攔截的是實現了Executor接口的query方法,后面的args配置的該方法的入參。在mybatis中默認攔截以下接口的方法,

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

Executor(執行器)、ParameterHandler(參數處理器)、ResultSetHandler(結果集處理器)、StatementHandler(SQL語法構建器)在mybatis中是四個頂級接口,貫穿了mybatis執行sql的全過程,從這里可以看出mybatis的攔截器的強大。

下面看下我的攔截器MyInterceptor的源碼,

package cn.com.mybatis.plugins;

import java.util.Properties;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

@Intercepts({@Signature(type=Executor.class,method="query",args= {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class MyInterceptor implements Interceptor{

    /**
     * incation 封裝了攔截的類,方法,方法參數
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // TODO Auto-generated method stub
        //執行被攔截的類中的方法
        
        
        Object o=invocation.proceed();
        return o;
    }

    /**
     * 返回一個JDK代理對象
     */
    @Override
    public Object plugin(Object target) {
        // TODO Auto-generated method stub
        return Plugin.wrap(target, this);
    }
/**
 * 允許在配置文件中配置一些屬性即
 * <plugin interceptor="">
 * <property name ="" value =""/>
 * </plugin>
 */
    @Override
    public void setProperties(Properties properties) {
        // TODO Auto-generated method stub
        
    }

}

 

二、詳述

上面,了解了plugis標簽的基本配置及使用,下面看mybatis是如何解析的。

在XMLConfigBuilder類中的parseConfiguration方法

private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      //解析properties標簽    
      propertiesElement(root.evalNode("properties"));
      //解析settings標簽,1、把<setting>標簽解析為Properties對象
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      /*2、對<settings>標簽中的<setting>標簽中的內容進行解析,這里解析的是<setting name="vfsImpl" value=",">
      * VFS是mybatis中用來表示虛擬文件系統的一個抽象類,用來查找指定路徑下的資源。上面的key為vfsImpl的value可以是VFS的具體實現,必須
      * 是權限類名,多個使用逗號隔開,如果存在則設置到configuration中的vfsImpl屬性中,如果存在多個,則設置到configuration中的僅是最后一個
      * */
      loadCustomVfs(settings);
      //解析別名標簽,例<typeAlias alias="user" type="cn.com.bean.User"/>
      typeAliasesElement(root.evalNode("typeAliases"));
      //解析插件標簽
      pluginElement(root.evalNode("plugins"));
      //解析objectFactory標簽,此標簽的作用是mybatis每次創建結果對象的新實例時都會使用ObjectFactory,如果不設置
      //則默認使用DefaultObjectFactory來創建,設置之后使用設置的
      objectFactoryElement(root.evalNode("objectFactory"));
      //解析objectWrapperFactory標簽
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //解析reflectorFactory標簽
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      //解析environments標簽
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      //解析<mappers>標簽
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

從上面的代碼中,我們找到下面這行解析plugins標簽的代碼,

 //解析插件標簽
 pluginElement(root.evalNode("plugins"));

上面折行代碼即在解析配置文件中的plugis標簽,其定義如下

/**
 * 解析<plugin>標簽
 * @param parent
 * @throws Exception
 */
  private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
//1、獲得interceptor屬性 String interceptor
= child.getStringAttribute("interceptor");
//2、把plugin標簽下的property標簽的內容解析為Properties對象 Properties properties
= child.getChildrenAsProperties(); //3、使用配置的interceptor生成一個實例,配置的interceptor需要實現interceptor接口 Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); //4、執行interceptor的setProperties方法,把配置的properties標簽中的值設置到setProperties中 interceptorInstance.setProperties(properties);
//5、添加interceptor configuration.addInterceptor(interceptorInstance); } } }

1、解析標簽

從上面的代碼中可以看到在循環plugin標簽,取得interceptor配置的類的全限類名,然后獲得plugin標簽下的property標簽的內容,調用getChildrenAsProperties方法,

public Properties getChildrenAsProperties() {
    Properties properties = new Properties();
    for (XNode child : getChildren()) {
      String name = child.getStringAttribute("name");
      String value = child.getStringAttribute("value");
      if (name != null && value != null) {
        properties.setProperty(name, value);
      }
    }
    return properties;
  }

解析完property標簽后,下面會生成interceptor實例。

2、生成interceptor實例

上面解析完plugin標簽的內容后會生成interceptor實例,且調用其setProperty方法,

//使用配置的interceptor生成一個實例,配置的interceptor需要實現interceptor接口
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        //執行interceptor的setProperties方法,把配置的properties標簽中的值設置到setProperties中
        interceptorInstance.setProperties(properties);

 從上面的代碼可以看到把配置的interceptor全限類名解析為一個Class對象,然后調用其默認的構造方法獲得一個實例,然后調用了其setProperties方法,即把在標簽中配置的property屬性值注入到interceptor實例中,在我的例子中即調用下面的方法,

/**
 * 允許在配置文件中配置一些屬性即
 * <plugin interceptor="">
 * <property name ="" value =""/>
 * </plugin>
 */
    @Override
    public void setProperties(Properties properties) {
        // TODO Auto-generated method stub
        this.properties=properties;
    }

這樣就可以把標簽中的內容轉化到interceptor實例中的屬性。

 3、添加到configuration中

經過上面的兩個步驟的分析,攔截器已經從配置文件中的配置轉化為了一個實例,下面就是要放入核心配置類configuration中,

configuration.addInterceptor(interceptorInstance);

看addInterceptor方法,該方法是configuration類中的方法,

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

調用了interceptorChain的addInterceptor方法,interceptorChain使用下面的方式進行初始化,

//攔截器鏈
  protected final InterceptorChain interceptorChain = new InterceptorChain();

其addInterceptor方法如下,

可以看到最終放到了interceptors中,這是一個ArrayList。

最終完成了解析plugins標簽並把攔截器實例放到configuration中的過程。

三、總結

本文分析了mybatis中plugins標簽的解析過程,該標簽的解析過程相對簡單,重點是攔截器背后的原理,是如何起到攔截作用,待下次分析。

 

有不正之處,歡迎指正,感謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM