在前邊的博客在分析了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標簽的解析過程,該標簽的解析過程相對簡單,重點是攔截器背后的原理,是如何起到攔截作用,待下次分析。
有不正之處,歡迎指正,感謝!