在 mybatis源碼分析-環(huán)境搭建 一文中,我們的測試代碼如下:
public static void main(String[] args) throws IOException { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); try { DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); List<Dept> deptList = deptMapper.getAllDept(); System.out.println(deptList); } finally { sqlSession.close(); } }
mybatis源碼分析-SqlSessionFactory構(gòu)建過程 一文中探究了 SqlSessionFactory
對象的生成方式,但是那里還有兩行代碼沒有仔細研究,因為這兩行代碼涉及的東西有些多,這篇文章主要研究這兩行代碼背后的細節(jié)。代碼再貼一遍:
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); return build(parser.parse());
需要研究的第一行代碼只是生成了一個 XMLConfigBuilder
對象而已。
public XMLConfigBuilder(InputStream inputStream) { this((InputStream)inputStream, (String)null, (Properties)null); }
繼續(xù)看構(gòu)造重載函數(shù):
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) { this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props); }
這里繼續(xù)調(diào)用了另一個構(gòu)造函數(shù),只是入?yún)⒆優(yōu)?XPathParser
對象。
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { super(new Configuration()); this.localReflectorFactory = new DefaultReflectorFactory(); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; }
這個構(gòu)造函數(shù)主要是賦值功能,給 XMLConfigBuilder
對象的屬性賦值。注意 XMLConfigBuilder extends BaseBuilder
,而 BaseBuilder
含有 Configuration
屬性 , 因此 XMLConfigBuilder
需要給一個默認的 Configuration
值,就是下面這行代碼的功能:
super(new Configuration());
看上面的流程,似乎也不復(fù)雜,這里漏了一點 XPathParser
的創(chuàng)建,只有創(chuàng)建好了 XPathParser
才能進行文件解析,然后生成對應(yīng)的 Configuration
對象。
XPathParser
使用了 xPath
解析技術(shù)。
xml解析的技術(shù)有很多,以前用過 dom4j,當(dāng)使用dom4j查詢比較深的層次結(jié)構(gòu)的節(jié)點(標(biāo)簽,屬性,文本)時比較麻煩!使用xPath主要是用于快速獲取所需的節(jié)點對象。
本文不打算講解如何把 inputStream
轉(zhuǎn)為 XPathParser
對象,有興趣可以自學(xué)一下。
上面講解的代碼主要掌握創(chuàng)建 XMLConfigBuilder
對象時有一個特別重要的屬性 XPathParser
,這個屬性可以快速獲取 xml
的各種元素,方便后續(xù)操作。
根據(jù)上下文,這里的 parser
就是 XMLConfigBuilder
,我們來看下 parse()
方法做了什么:
public Configuration parse() { if (this.parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } else { this.parsed = true; this.parseConfiguration(this.parser.evalNode("/configuration")); return this.configuration; } }
注意這段代碼的核心是:
this.parseConfiguration(this.parser.evalNode("/configuration"));
注意:this.parser
指的是 XMLConfigBuilder
里的 XPathParser
對象。evalNode
方法獲取全局配置文件里面 Configuration
下面的所有內(nèi)容?,F(xiàn)在想想全局配置文件的結(jié)構(gòu)吧。
再看下 parseConfiguration
方法:
private void parseConfiguration(XNode root) { try { propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); loadCustomLogImpl(settings); typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " \+ e, e); } }
全局配置文件里面的每一個屬性,都有對應(yīng)的方法進行解析。解析成一個一個對象賦值給最大的那個 Configuration
對象,到此就完事了。
上面的解析配置文件的方法很多,本文不會把所有的解析代碼都一一探究,就使用插件解析代碼進行舉例說明吧。也就是下面這行代碼:
pluginElement(root.evalNode("plugins"));
在研究源碼之前,我們先了解下插件機制,下面的內(nèi)容都是從官網(wǎng)復(fù)制的:
MyBatis 允許你在已映射語句執(zhí)行過程中的某一點進行攔截調(diào)用。默認情況下,MyBatis 允許使用插件來攔截的方法調(diào)用包括:
這些類中方法的細節(jié)可以通過查看每個方法的簽名來發(fā)現(xiàn),或者直接查看 MyBatis 發(fā)行包中的源代碼。 如果你想做的不僅僅是監(jiān)控方法的調(diào)用,那么你最好相當(dāng)了解要重寫的方法的行為。 因為如果在試圖修改或重寫已有方法的行為的時候,你很可能在破壞 MyBatis 的核心模塊。 這些都是更低層的類和方法,所以使用插件的時候要特別當(dāng)心。
通過 MyBatis 提供的強大機制,使用插件是非常簡單的,只需實現(xiàn) Interceptor 接口,并指定想要攔截的方法簽名即可。
// ExamplePlugin.java @Intercepts({@Signature( type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})}) public class ExamplePlugin implements Interceptor { private Properties properties = new Properties(); public Object intercept(Invocation invocation) throws Throwable { // implement pre processing if need Object returnObject = invocation.proceed(); // implement post processing if need return returnObject; } public void setProperties(Properties properties) { this.properties = properties; } }
下面是如何配置:
<!-- mybatis-config.xml --> <plugins> <plugin interceptor="org.mybatis.example.ExamplePlugin"> <property name="someProperty" value="100"/> </plugin> </plugins>
現(xiàn)在我們想如何把上面配置文件的代碼解析出來,源碼如下:
private void pluginElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { String interceptor = child.getStringAttribute("interceptor"); Properties properties = child.getChildrenAsProperties(); Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance(); interceptorInstance.setProperties(properties); configuration.addInterceptor(interceptorInstance); } } }
由于 plugin 可以有多個,因此代碼循環(huán)解析,對于每一個 plugin,首先拿到屬性 interceptor,也就是自定義插件的實現(xiàn)類,如上面官網(wǎng)的例子 ExamplePlugin
,通過反射生成對象實例,該對象有個屬性 Properties
,也就是 mybatis-config.xml 中的
<property name="someProperty" value="100"/>
內(nèi)容,只不過被
child.getChildrenAsProperties()
進行解析成鍵值對形式的 Properties
對象,代碼如下
public Properties getChildrenAsProperties() { Properties properties = new Properties(); Iterator var2 = this.getChildren().iterator(); while(var2.hasNext()) { XNode child = (XNode)var2.next(); String name = child.getStringAttribute("name"); String value = child.getStringAttribute("value"); if (name != null && value != null) { properties.setProperty(name, value); } } return properties; }
這段代碼比較簡單,循環(huán)取 name 和 value 的值給 Properties
對象而已。
對于mybatis-config.xml其它配置,也是通過類似的方式解析成相關(guān)對象,最終都賦值給 Configuration對象而已。