Mybatis是如何解析配置文件的?看完终于明白了

关注“Java后端技术全栈

回复“面试”获取全套面试资料

在以前文章中,我们把Mybatis源码阅读的整个流程梳理了一遍。今天,我们来详细聊聊,Mybatis是如何解析配置文件的。

这是今天分析的流程图:

还是从案例开始。

demo案例

  1.    public static void main(String[] args) {
  2.         String resource = "mybatis-config.xml";
  3.         InputStream inputStream = null;
  4.         SqlSession sqlSession = null;
  5.         try {
  6.             inputStream = Resources.getResourceAsStream(resource);
  7.             SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  8.             sqlSession = sqlSessionFactory.openSession();
  9.             UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  10.             System.out.println(userMapper.selectById(1));
  11.         } catch (Exception e) {
  12.             e.printStackTrace();
  13.         } finally {
  14.             try {
  15.                 inputStream.close();
  16.             } catch (IOException e) {
  17.                 e.printStackTrace();
  18.             }
  19.             sqlSession.close();
  20.         }
  21.     }

见证奇迹

SqlSessionFactoryBuilder开始。

SqlSessionFactoryBuilder类

org.apache.ibatis.session.SqlSessionFactoryBuilder

该类里全是build方法各种重载。


  1. //这个方法啥也没干  
  2. public SqlSessionFactory build(InputStream inputStream) {
  3.     return build(inputStream, null, null);
  4.  }

最终来到另外一个build方法里:

  1.   public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  2.     try {
  3.       //创建一个XMLConfigBuilder对象  
  4.       XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
  5.       return build(parser.parse());
  6.     } catch (Exception e) {
  7.       throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  8.     } finally {
  9.       ErrorContext.instance().reset();
  10.       try {
  11.         inputStream.close();
  12.       } catch (IOException e) {
  13.         // Intentionally ignore. Prefer previous error.
  14.       }
  15.     }
  16.   }

XMLConfigBuilder类

该类的构造方法重载:

首先进入:

  1. public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
  2.     this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment,     
  3.          props);
  4. }
  5. private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
  6.     super(new Configuration());
  7.     ErrorContext.instance().resource("SQL Mapper Configuration");
  8.     this.configuration.setVariables(props);
  9.     this.parsed = false;
  10.     this.environment = environment;
  11.     this.parser = parser;
  12. }

build(parser.parse());中的parser.parse();

mybatis-config.xml在哪里解析的呢?

请看下面这个方法:

  1. //该方法返回一个Configuration对象
  2. public Configuration parse() {
  3.   if (parsed) {
  4.     throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  5.   }
  6.   parsed = true;
  7.   //关键点
  8.   parseConfiguration(parser.evalNode("/configuration"));
  9.   return configuration;
  10. }

parseConfiguration(parser.evalNode("/configuration"));

终于看到开始解析配置文件了:

进入方法parseConfiguration。

  1.   private void parseConfiguration(XNode root) {
  2.     try {
  3.       //issue #117 read properties first
  4.       propertiesElement(root.evalNode("properties"));
  5.       Properties settings = settingsAsProperties(root.evalNode("settings"));
  6.       loadCustomVfs(settings);
  7.       loadCustomLogImpl(settings);
  8.       typeAliasesElement(root.evalNode("typeAliases"));
  9.       pluginElement(root.evalNode("plugins"));
  10.       objectFactoryElement(root.evalNode("objectFactory"));
  11.       objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
  12.       reflectorFactoryElement(root.evalNode("reflectorFactory"));
  13.       settingsElement(settings);
  14.       // read it after objectFactory and objectWrapperFactory issue #631
  15.       environmentsElement(root.evalNode("environments"));
  16.       databaseIdProviderElement(root.evalNode("databaseIdProvider"));
  17.       typeHandlerElement(root.evalNode("typeHandlers"));
  18.       mapperElement(root.evalNode("mappers"));
  19.     } catch (Exception e) {
  20.       throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  21.     }
  22.   }

这里就是把mybatis-config.xml内容解析,然后设置到Configuration对象中。

那么我们定义的Mapper.xml是在哪里解析的呢?

我们的Mapper.xml在mybatis-config.xml中的配置是这样的:

<mapper>使用方式有以下四种:

  1. //1使用类路径
  2. <mappers>
  3.     <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  4.       <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  5.    <mapper resource="org/mybatis/builder/PostMapper.xml"/>
  6. </mappers>
  7. //2使用绝对url路径
  8. <mappers>
  9.    <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  10.    <mapper url="file:///var/mappers/BlogMapper.xml"/>
  11.    <mapper url="file:///var/mappers/PostMapper.xml"/>
  12. </mappers>
  13. //3使用java类名
  14. <mappers>
  15.    <mapper class="org.mybatis.builder.AuthorMapper"/>
  16.    <mapper class="org.mybatis.builder.BlogMapper"/>
  17.    <mapper class="org.mybatis.builder.PostMapper"/>
  18. </mappers>
  19. //4自动扫描包下所有映射器
  20. <mappers>
  21.    <package name="org.mybatis.builder"/>
  22. </mappers>

继续源码分析,我们在上面mybatis-config.xml解析中可以看到:

我们不妨进入这个方法看看:

  1.  private void mapperElement(XNode parent) throws Exception {
  2.     if (parent != null) {
  3.       for (XNode child : parent.getChildren()) {
  4.         //自动扫描包下所有映射器
  5.         if ("package".equals(child.getName())) {
  6.           String mapperPackage = child.getStringAttribute("name");
  7.           //放  
  8.           configuration.addMappers(mapperPackage);
  9.         } else {
  10.           String resource = child.getStringAttribute("resource");
  11.           String url = child.getStringAttribute("url");
  12.           String mapperClass = child.getStringAttribute("class");
  13.           //使用java类名
  14.           if (resource != null && url == null && mapperClass == null) {
  15.             ErrorContext.instance().resource(resource);
  16.              //根据文件存放目录,读取XxxMapper.xml
  17.             InputStream inputStream = Resources.getResourceAsStream(resource);
  18.              //映射器比较复杂,调用XMLMapperBuilder
  19.             //注意在for循环里每个mapper都重新new一个XMLMapperBuilder,来解析
  20.             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
  21.             mapperParser.parse();
  22.           //使用绝对url路径
  23.           } else if (resource == null && url != null && mapperClass == null) {
  24.             ErrorContext.instance().resource(url);
  25.             InputStream inputStream = Resources.getUrlAsStream(url);
  26.             //映射器比较复杂,调用XMLMapperBuilder
  27.             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
  28.             mapperParser.parse();
  29.           //使用类路径    
  30.           } else if (resource == null && url == null && mapperClass != null) {
  31.             Class<?> mapperInterface = Resources.classForName(mapperClass);
  32.             //直接把这个映射加入配置
  33.             configuration.addMapper(mapperInterface);
  34.           } else {
  35.             throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
  36.           }
  37.         }
  38.       }
  39.     }
  40.   }

这里刚刚和我们的上面说的<mapper>使用的方式完全是一模一样的。

到这里,配置文件mybatis-config.xml和我们定义映射文件XxxMapper.xml就全部解析完成。

回到SqlSessionFactoryBuilder类

前面讲到了XMLConfigBuilder中的parse方法,并返回了一个Configuration对象。

build(parser.parse());

这个build方法就是传入一个Configuration对象,然后构建一个DefaultSqlSession对象。

  1.   public SqlSessionFactory build(Configuration config) {
  2.     return new DefaultSqlSessionFactory(config);
  3.   }

继续回到我们的demo代码中这一行代码里:

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

这一行代码就相当于:

SqlSessionFactory sqlSessionFactory = new new DefaultSqlSessionFactory();

到这里,我们的整个流程就搞定了。



欢迎关注公众号:Java后端技术全栈