转载

Mybatis加载配置文件源码理解

我猜测你最开始学习mybatis时肯定是通过XML来构建SqlSessionFactory对象的,或许有些迷茫,来看一下这行代码。

InputStream inputStream = Resources.getResourceAsStream("myBatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
复制代码

上面的代码就是构造出sqlSessionFactory的代码,通过sqlSessionFactory来创建我们的sqlSession对象,进而进行数据库操作。包括官网文档的入门文档中也是首先介绍了通过XML方式构建SqlSessionFactory。我们可以写一个单元测试来一起看一下,mybatis是如何通过XML来构建SqlSessionFactory对象的。

构建sqlSessionFactory

首先我们看一下SqlSessionFactoryBuilder类内部的结构,原来SqlSessionFactoryBuilder多次重载build方法利用建造者模式来创建我们最终的DefaultSqlSessionFactory对象。

Mybatis加载配置文件源码理解

我将类内代码精简一下,更加清楚的看清调用过程。

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,将xml转换为Configration对象

先不要关注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。复制代码
原文  https://juejin.im/post/5e1362885188253a97401984
正文到此结束
Loading...