Mybatis源码系列(四)Mybatis启动获取SqlSessionFactory的过程

316 阅读5分钟

Mybatis构建SqlSessionFactory的过程

概览

  • 常用的解析XML的方式
  • Java 解析XML案例代码
  • mybatis解析的具体过程
  • 总结

常用的解析XML的方式

mybatis是基于XML进行构建的,所以我们可以首先了解下,java中解析XML的几种方式

  1. DOM 方式:DOM 基于树形结构解析, 它会将整个文档读入内存并构建一个 DOM 树, 基于这棵树的结构对各个节点进行解析;
  2. SAX 方式:SAX 是基于事件模型的 XML 解析方式, 它不需要将整个 XML 文档加载到内存中, 而只需要将一部分 XML 文档的一部分加载到内存中, 即可开始解析;
  3. StAX 方式:StAX 与 SAX 类似, 也是把 XML 文档作为一个事件流进行处理, 但不同之处在于 StAX 采用的是“拉模式”, 即应用程序通过调用解析器推进解析的过程。

mybatis在加载mybatis-config.xml的时候,使用的是DOM的Xpath方式进行XML的解析。

下面就是一个java解析XML的案例

Java 解析XML案例代码

student.xml

<STUDENT>
    <CD id="1">
        <NAME>ZHANGSAN</NAME>
    </CD>
    <CD id="6">
        <NAME>LISI</NAME>
    </CD>
    <CD id="8">
        <NAME>WANGER</NAME>
    </CD>
    <CD id="10">
        <NAME>MAZI</NAME>
    </CD>
    <CD id="13">
        <NAME>FENQING</NAME>
    </CD>
</STUDENT>

Parse.java

import org.apache.ibatis.io.Resources;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import java.io.InputStream;

/**
 * @version v1.0
 * @ProjectName: mybatis-sources
 * @ClassName: Parse
 * @Description: TODO(一句话描述该类的功能)
 * @Author: Aby1993
 * @Date: 2020/4/7 21:42
 */
public class Parse {

    public static void main(String[] args) throws Exception {
        
        DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();

        builderFactory.setValidating(false);
        builderFactory.setNamespaceAware(false);
        builderFactory.setIgnoringComments(true);
        builderFactory.setIgnoringElementContentWhitespace(false);
        builderFactory.setCoalescing(false);
        builderFactory.setExpandEntityReferences(true);

        DocumentBuilder builder = builderFactory.newDocumentBuilder();

        builder.setErrorHandler(new ErrorHandler() {
            @Override
            public void warning(SAXParseException exception) throws SAXException {
                System.out.println("warning:" + exception.getMessage());
            }

            @Override
            public void error(SAXParseException exception) throws SAXException {
                System.out.println("error:" + exception.getMessage());
            }

            @Override
            public void fatalError(SAXParseException exception) throws SAXException {
                System.out.println("fatalError:" + exception.getMessage());
            }
        });

        String resource = "student.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        Document document = builder.parse(inputStream);

        XPathFactory xPathFactory = XPathFactory.newInstance();
        XPath xPath = xPathFactory.newXPath();
        //获取CD->NAME的数值
        XPathExpression expression = xPath.compile("//CD//NAME//text()");
  
        Object result = expression.evaluate(document, XPathConstants.NODESET);
        NodeList nodeList = (NodeList) result;

        for (int i = 0; i < nodeList.getLength(); i++) {
            System.out.println(nodeList.item(i).getNodeValue());
        }
    }
}

执行结果

ZHANGSAN
LISI
WANGER
MAZI
FENQING

关于java解析XML网上有不少案例,作为基础,这一块儿还是应该重点掌握的,我本人对这一块儿也是不太熟,还是因为看源码了,各种解析XML的过程,看了相关的文档和博文,才算是渐渐的稍微理解一点儿,想要掌握还需要再接再厉才行。

mybatis解析的具体过程

测试代码

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
 //虽然说解析流是为了获取SqlSessionFactory ,但是实际上其实是为了获取里面的Configuration
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

根据SqlSessionFactoryBuilder().build(inputStream)方法进入到对应的org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties)方法中,代码如下:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      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.
      }
    }
  }

注意XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);方法进去,有两个比较重要的方法,需要大家仔细看看,可以看到其中的方法明显有上面我们解析XML案例中的一些代码,具体细节我们就不讲了:

 public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
    //注意看看实例化了什么
    commonConstructor(validation, variables, entityResolver);
    //可以看到有如同上方案例的代码
    this.document = createDocument(new InputSource(inputStream));
  }

通过parser.parse()我们进入到方法org.apache.ibatis.builder.xml.XMLConfigBuilder#parse中,具体代码如下:

  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //获取mybatis-config.xml中configuration节点下的数据
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

我们通过代码parseConfiguration(parser.evalNode("/configuration"));进入到org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration方法中,主要是解析配置文件中各个节点的数据,代码如下:

private void parseConfiguration(XNode root) {
    try {
      Properties settings = settingsAsPropertiess(root.evalNode("settings"));
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectionFactoryElement(root.evalNode("reflectionFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      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);
    }
  }

对于以上的解析过程,我们只看一个解析mapper的过程,通过代码 mapperElement(root.evalNode("mappers")); 我们进入到org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement方法中,代码如下:

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

通过以上代码,我们能够看到,mybatis在解析mapper时候的解析顺序,以及将mapper接口和mapper.xml之间通过namespace关联起来的一个过程,具体在方法org.apache.ibatis.builder.xml.XMLMapperBuilder#bindMapperForNamespace中,具体代码如下:

private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
          // Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          configuration.addLoadedResource("namespace:" + namespace);
          configuration.addMapper(boundType);
        }
      }
    }
  }

总结

好了,今天这个mybatis解析XML的过程,就说到这里,我们能够看到,虽然说SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);这句代码最终获取的是SqlSessionFactory,但是实际上整个过程,就是装配Configuration的过程,所以获取SqlSessionFactory是现象,而装配Configuration是本质。另外虽然说mybatis源码相对简单一些,但是我们还是能够看到想要完全看懂还是有点儿困难,对我来说,所谓的框架就是完全灵活运用了java基础和各种设计模式的产物,框架看的吃力,说明我们对于java基础和设计模式掌握的还是不够,所以知道问题在哪里后,我们需要在平时多花点儿时间巩固基础,对于mybatis还没有用过的朋友,可以参考中文网站mybatis网站,好了,就这些。