我猜测你最开始学习mybatis时肯定是通过XML来构建SqlSessionFactory对象的,或许有些迷茫,来看一下这行代码。
InputStream inputStream = Resources.getResourceAsStream("myBatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 复制代码
上面的代码就是构造出sqlSessionFactory的代码,通过sqlSessionFactory来创建我们的sqlSession对象,进而进行数据库操作。包括官网文档的入门文档中也是首先介绍了通过XML方式构建SqlSessionFactory。我们可以写一个单元测试来一起看一下,mybatis是如何通过XML来构建SqlSessionFactory对象的。
首先我们看一下SqlSessionFactoryBuilder类内部的结构,原来SqlSessionFactoryBuilder多次重载build方法利用建造者模式来创建我们最终的DefaultSqlSessionFactory对象。
我将类内代码精简一下,更加清楚的看清调用过程。
public SqlSessionFactory build(InputStream inputStream, Properties properties) { // 1. 调用重载方法,进行实际操作。 return build(inputStream, null, properties); } public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { // 2. 将xml配置文件转换成XMLConfigBuilder对象,初始化XpathParse XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // 3. 利用Xpath进行解析,返回Configration对象,创建DefaultSqlSessionFactory对象 return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } } public SqlSessionFactory build(Configuration config) { // 4. 创建DefaultSqlSessionFactory对象,并返回。 return new DefaultSqlSessionFactory(config); } 复制代码
先不讨论细节,大家对上面代码每一步的功能有个大致的了解。总结一下就是: 读取xml配置文件 -> 将输入流转换为可操作对象 -> 将可操作的对象进行解析,得到配置文件对象 -> 根据配置文件内的配置新建SqlSessionFactory对象 。这里的配置文件对象就可以理解为xml配置文件以一种java能直接操作的形式存在。
先不要关注XMLConfigBuilder是怎么来的,大家先记住这对象里面有个和 XPath 相关的对象来进行实际解析操作即可。我将代码贴出来,咱们天才一步一步看。
首先我们调用了 XMLConfigBuilder 类内的 parse() 方法获取Configuration对象。
public Configuration parse() { // 1. 验证这个对象没有被多次解析过,parsed是在类内第一行定义的一个布尔变量。 if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; // 2. 调用神秘方法,继续解析。 parseConfiguration(parser.evalNode("/configuration")); return configuration; } 复制代码
在这里大家一定好奇 parser.evalNode("/configuration") 这句话。好吧,避不开了,先说一下 parser 这个东西是怎么来的。上一节构建sqlSessionFactory中的代码 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties) 这行代码主要进行了一些初始化工作,最重要的就是初始化了XPathParser对象,这个对象就是上面提到的XPath相关。 通过XPathParser进行了配置文件中每个结点的解析 。下面就是代码,大家简单看一下就好,反正重点也不在这里。
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) { // 1. 初始化XPathParser对象然后调用构造方法初始化XMLConfigBuilder对象,这个对象就是parser.evalNode("/configuration")的parse对象。 this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props); } private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { //2. 新建一个Configration对象,接收xml配置 super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; // 这个parser就是parser.evalNode("/configuration")的parse对象。 this.parser = parser; } 复制代码
evalNode("/configuration")这个方法是XPathParser类内的方法,贴了个简单的代码,大概来说就是传入的参数是xml配置文件中的一个结点,解析配置文件后得到这个结点的XNode对象(理解为配置文件的java可操作的形式即可)。
public XNode evalNode(String expression) { return evalNode(document, expression); } 复制代码
我们了解了XNode对象之后再来看神秘方法:parseConfiguration方法,看名字就知道了,他才是解析configration的真正入口
private void parseConfiguration(XNode root) { try { //issue #117 read properties first //issue #117 read properties first // <1> 解析 <properties /> 标签 propertiesElement(root.evalNode("properties")); // <2> 解析 <settings /> 标签 Properties settings = settingsAsProperties(root.evalNode("settings")); // <3> 加载自定义 VFS 实现类 loadCustomVfs(settings); // <4> 解析 <typeAliases /> 标签 typeAliasesElement(root.evalNode("typeAliases")); // <5> 解析 <plugins /> 标签 pluginElement(root.evalNode("plugins")); // <6> 解析 <objectFactory /> 标签 objectFactoryElement(root.evalNode("objectFactory")); // <7> 解析 <objectWrapperFactory /> 标签 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); // <8> 解析 <reflectorFactory /> 标签 reflectorFactoryElement(root.evalNode("reflectorFactory")); // <9> 赋值 <settings /> 到 Configuration 属性 settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 // <10> 解析 <environments /> 标签 environmentsElement(root.evalNode("environments")); // <11> 解析 <databaseIdProvider /> 标签 databaseIdProviderElement(root.evalNode("databaseIdProvider")); // <12> 解析 <typeHandlers /> 标签 typeHandlerElement(root.evalNode("typeHandlers")); // <13> 解析 <mappers /> 标签 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } } 复制代码
看注释就能了解到,这个方法是真正解析配置文件内各个标签的真正入口,感兴趣的可以自行看一下里面的内容。在这个方法内依次调用类内的结点解析方法,丰富我们的configration对象。
这边文章写的比较简单,大致了解了mybatis是如何解析配置的。最后我们总结一下:
1. 在SqlSessionFactory的build方法中,传入xml配置文件,利用XMLConfigBuilder解析配置。 2. 在初始化XMLConfigBuilder对象的过程中:初始化了Configration对象用于接收xml配置、初始化XPathParser对象用于解析xml配置文件。 3. 由XPathParser对象对象解析xml配置文件,获得根结点configration XNode。 4. 将configration XNode作为参数,调用parseConfiguration依次解析内部子结点,将解析后的结果存入Configratuon对象中。 5. 集各项配置于一身的Configration对象来初始化出SqlSessionFactory。复制代码