在 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对象而已。