1. 程式人生 > >mybatis源碼-解析配置文件(二)之解析的流程

mybatis源碼-解析配置文件(二)之解析的流程

close ria 操作 exp 記得 resource build reset sed

1. 簡介

在之前的文章《mybatis 初步使用(IDEA的Maven項目, 超詳細)》中, 講解了mybatis的初步使用, 並總結了以下mybatis的執行流程:

  1. 通過 Resources 工具類讀取 mybatis-config.xml, 存入 Reader;
  2. SqlSessionFactoryBuilder 使用上一步獲得的 reader 創建 SqlSessionFactory 對象;
  3. 通過 sqlSessionFactory 對象獲得 SqlSession;
  4. SqlSession對象通過 *Mapper 方法找到對應的 SQL 語句, 執行 SQL 查詢。
  5. 底層通過 JDBC 查詢後獲得 ResultSet, 對每一條記錄, 根據resultMap的映射結果映射到 Student 中, 返回 List。
  6. 最後記得關閉 SqlSession

本系列文章深入講解第 2 步, 解析配置文件。


2. 配置文件解析流程分析

2.1 調用

配置文件的解析過程對應的是以下的代碼:

 Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

很簡單的兩句代碼:

  1. 通過mybatis的資源類Resources讀入“mybatis-config.xml”文件;
  2. 使用SqlSessionFactoryBuilder類生成我們需要的SqlSessionFactory類;(真正的解析只有這一過程)

2.2 解析的目的

要理解配置文件的解析過程, 首先要明白解析的目的是什麽, 從最直觀的調用代碼來看, 是獲得SqlSessionFactory

但是, 從源代碼來看, 更本質的應該這麽說:

mybatis解析配置文件最本質的目的是為了獲得Configuration對象

Configuration 對象, 可以理解是mybatisXML文件在程序中的化身。

2.3 XML 解析流程

build(reader)函數裏面包含著SqlSessionFactory

的創建邏輯。

從客戶端調用build(reader)函數到返回SqlSessionFactory, 可以用如下的時序圖表示:
技術分享圖片

下面來看看各個步驟, 請記住,mybatis解析配置文件的本質就是獲得Configuration對象

2.3.1 build(parser)

其最終調用以下的方法

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
}

該方法:

  1. 創建XMLConfigBuilder對象;
  2. 使用XMLConfigBuilder對象的方法parse()來獲得Confiuration對象;
  3. 通過build(configuration), 使用Confiuration對象創建相應的SqlSessionFactory對象。

2.3.2 new XMLConfigBuilder(...);

new XMLConfigBuilder(reader, environment, properties)方法, 從字面上來理解就是創建一個XMLConfigBuilder對象。

public XMLConfigBuilder(Reader reader, String environment, Properties props) {
    this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}

其最終調用的方法是這個:

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
}

XMLConfigBuilder類繼承於BaseBuilder類, super(new Configuration())對應的方法:

public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}

也就是給BaseBuilder類的各個成員變量賦值而已。

裏面的XpathParser對象是通過new XPathParser(reader, true, props, new XMLMapperEntityResolver())方法而來的。

2.3.3 new XPathParser(...)

new XPathParser(reader, true, props, new XMLMapperEntityResolver())就是創建XpathParser的過程。

public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = createDocument(new InputSource(reader));
}

調用了以下兩個函數:

private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
    this.validation = validation;
    this.entityResolver = entityResolver;
    this.variables = variables;
    XPathFactory factory = XPathFactory.newInstance();
    this.xpath = factory.newXPath();
}
private Document createDocument(InputSource inputSource) {
    // important: this must only be called AFTER common constructor
    try {
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      factory.setValidating(validation);

      factory.setNamespaceAware(false);
      factory.setIgnoringComments(true);
      factory.setIgnoringElementContentWhitespace(false);
      factory.setCoalescing(false);
      factory.setExpandEntityReferences(true);

      DocumentBuilder builder = factory.newDocumentBuilder();
      builder.setEntityResolver(entityResolver);
      builder.setErrorHandler(new ErrorHandler() {
        @Override
        public void error(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void fatalError(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void warning(SAXParseException exception) throws SAXException {
        }
      });
      return builder.parse(inputSource);
    } catch (Exception e) {
      throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    }
}

註意這兩個函數是有先後順序的, createDocument函數務必在commonConstructor函數之後執行

createDocument函數, 其實就是通過 DOM 解析 XML 文件的過程中的幾個步驟,獲得document, 具體可以參見 「mybatis 解析配置文件(一)之XML的DOM解析方式」, 裏面提到了 Java 中使用 DOM 解析 XML 的步驟, 大致如下:

  1. 創建 DocumentBuilderFactory 對象;
  2. 通過 DocumentBuilderFactory 創建DocumentBuilder對象;
  3. 通過DocumentBuilder, 從文件或流中創建通過Document對象;
  4. 創建XPathFactory對象, 並通過XPathFactory創建XPath對象;
  5. 通過XPath解析出XPathExpression對象;
  6. 使用XPathExpression在文檔中搜索出相應的節點。

剛剛提到的兩個函數, 已經完成了前4部分, 獲得了Document對象, Xpath對象, 並返回後將其賦值給了相應的成員變量。

也就是說, 到了這一步, 我們已經獲得了XpathParser對象, 該對象中已經含有 mybatis-config.xml 文件對應的 Document Object, 即documentxpath。 通過documentxpath,我們可以對 mybatis-config.xml 進行後兩部操作操作。

後面幾個步驟, 是在XMLConfiguration對象的parse()函數中使用到, 詳情見 2.3.5

2.3.4 new Configuration()

之前提到過, 配置文件解析的本質就是獲得Configuration對象

現在, Configuration對象在解析的過程中第一次出現了。

那我們就可以返回這個對象了?

當然不是, 這個對象現在只是創建, 後續還有很多成員變量需要根據 XML 配置文件解析後來賦值。

2.3.5 parser.parse()

這裏的parserXMLConfigBuilder對象。

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}

這個函數返回的Configuration對象就是最終寫入SqlSessionFatory對應成員變量的對象。

由於配置文件解析的本質就是獲得Configuration對象, 因此, 這個函數就是解析的核心。

private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      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);
    }
}

其對應的過程就是解析 XML 配置文件中 properties, settings, typeAliases, plugins, objectFactory, objectWrapperFactory, reflectorFactory, environments, databaseIdProvider, typeHandlers, mappers, 這些子節點。

其中的evalNode函數, 在其函數過程中, 會調用XParhParser中的函數, 對 xml 節點進行解析:

private Object evaluate(String expression, Object root, QName returnType) {
    try {
      return xpath.evaluate(expression, root, returnType);
    } catch (Exception e) {
      throw new BuilderException("Error evaluating XPath.  Cause: " + e, e);
    }
}

以上過程就是我們 2.3.3 中提到的第 5, 6 步過程。

具體的在後續的文章中在深入了解。

2.3.6 build(configuration)

該函數就是創建一個具體的SqlSessionFactory對象。

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}
public DefaultSqlSessionFactory(Configuration configuration) {
    this.configuration = configuration;
}

就是創建DefaultSqlSessionFactory對象, 並將configuration賦值給相應的成員變量。


更具體的解析配置的過程, 後續分享。

mybatis源碼-解析配置文件(二)之解析的流程