在 mybatis源碼分析-環境搭建 一文中,咱們的測試代碼以下:java
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構建過程 一文中探究了 SqlSessionFactory
對象的生成方式,可是那裏還有兩行代碼沒有仔細研究,由於這兩行代碼涉及的東西有些多,這篇文章主要研究這兩行代碼背後的細節。代碼再貼一遍:sql
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); return build(parser.parse());
須要研究的第一行代碼只是生成了一個 XMLConfigBuilder
對象而已。segmentfault
public XMLConfigBuilder(InputStream inputStream) { this((InputStream)inputStream, (String)null, (Properties)null); }
繼續看構造重載函數:mybatis
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) { this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props); }
這裏繼續調用了另外一個構造函數,只是入參變爲 XPathParser
對象。app
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; }
這個構造函數主要是賦值功能,給 XMLConfigBuilder
對象的屬性賦值。注意 XMLConfigBuilder extends BaseBuilder
,而 BaseBuilder
含有 Configuration
屬性 , 所以 XMLConfigBuilder
須要給一個默認的 Configuration
值,就是下面這行代碼的功能:dom
super(new Configuration());
看上面的流程,彷佛也不復雜,這裏漏了一點 XPathParser
的建立,只有建立好了 XPathParser
才能進行文件解析,而後生成對應的 Configuration
對象。ide
XPathParser
使用了 xPath
解析技術。函數
xml解析的技術有不少,之前用過 dom4j,當使用dom4j查詢比較深的層次結構的節點(標籤,屬性,文本)時比較麻煩! 使用xPath主要是用於快速獲取所需的節點對象。
本文不打算講解如何把 inputStream
轉爲 XPathParser
對象,有興趣能夠自學一下。源碼分析
上面講解的代碼主要掌握建立 XMLConfigBuilder
對象時有一個特別重要的屬性 XPathParser
,這個屬性能夠快速獲取 xml
的各類元素,方便後續操做。post
根據上下文,這裏的 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
下面的全部內容。如今想一想全局配置文件的結構吧。
再看下 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); } }
全局配置文件裏面的每個屬性,都有對應的方法進行解析。解析成一個一個對象賦值給最大的那個 Configuration
對象,到此就完事了。
上面的解析配置文件的方法不少,本文不會把全部的解析代碼都一一探究,就使用插件解析代碼進行舉例說明吧。也就是下面這行代碼:
pluginElement(root.evalNode("plugins"));
在研究源碼以前,咱們先了解下插件機制,下面的內容都是從官網複製的:
MyBatis 容許你在已映射語句執行過程當中的某一點進行攔截調用。默認狀況下,MyBatis 容許使用插件來攔截的方法調用包括:
這些類中方法的細節能夠經過查看每一個方法的簽名來發現,或者直接查看 MyBatis 發行包中的源代碼。 若是你想作的不只僅是監控方法的調用,那麼你最好至關了解要重寫的方法的行爲。 由於若是在試圖修改或重寫已有方法的行爲的時候,你極可能在破壞 MyBatis 的核心模塊。 這些都是更低層的類和方法,因此使用插件的時候要特別小心。
經過 MyBatis 提供的強大機制,使用插件是很是簡單的,只需實現 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>
如今咱們想如何把上面配置文件的代碼解析出來,源碼以下:
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 能夠有多個,所以代碼循環解析,對於每個 plugin,首先拿到屬性 interceptor,也就是自定義插件的實現類,如上面官網的例子 ExamplePlugin
,經過反射生成對象實例,該對象有個屬性 Properties
,也就是 mybatis-config.xml 中的
<property name="someProperty" value="100"/>
內容,只不過被
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; }
這段代碼比較簡單,循環取 name 和 value 的值給 Properties
對象而已。
對於mybatis-config.xml其它配置,也是經過相似的方式解析成相關對象,最終都賦值給 Configuration對象而已。