1. 程式人生 > >mybatis原始碼學習之執行過程分析(2)——config.xml配置檔案和mapper.xml對映檔案解析過程

mybatis原始碼學習之執行過程分析(2)——config.xml配置檔案和mapper.xml對映檔案解析過程

在上一篇中跟蹤了SqlSessionFactory及SqlSession的建立過程。這一篇,主要跟蹤Mapper介面和XML檔案對映及獲取。

1.xml檔案的解析

1.1Mybatis-config.xml的解析

在SqlSessionFactoryBuilder中執行build()方法時,其實做了配置檔案的載入和解析,以及Configuration的初始化。

SqlSessionFactoryBuilder.java

  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try
{ //在這裡例項化XMLConfigBuilder 時進行配置檔案的載入。 //parser.parse()實現配置檔案的解析,以及Configuration的初始化。 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. } } }

呼叫XMLConfigBuilder的構造器:

XMLConfigBuilder.java

  //構造方法,用來例項化XMLConfigBuilder
  public XMLConfigBuilder
(Reader reader, String environment, Properties props) { //呼叫了構造方法 this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props); } //當XPathParser例項建立後實際呼叫了該構造方法例項化XMLConfigBuilder private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { super(new Configuration()); //重點:在這裡建立了Configuration的例項 ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); // 設定parsed狀態為false,會在public Configuration parse()中做判斷,保證配置檔案只會被解析一次 this.parsed = false; this.environment = environment; this.parser = parser; }

在這裡 new XMLMapperEntityResolver() 主要用來離線檢查mybatis配置檔案DTDs,用來約束mybatis中的xml檔案的正確性的。

再來看XPathParser

XPathParser.java

public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = createDocument(new InputSource(reader));    //解析並生成文件。
    //mybatis-config.xml中配置的各項引數被儲存在DeferredDocumentImpl中的 protected transient Object fNodeName[][]; 和 protected transient Object fNodeValue[][];中。如圖

  }

  private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
    this.validation = validation;
    this.entityResolver = entityResolver;
    this.variables = variables;
    XPathFactory factory = XPathFactory.newInstance();    //通過XPathFactory工程建立XPath例項。XPath用來解析XML檔案。
    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 可以通過XML建立Document
      */
      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);    //使用DocumentBuilder將XML檔案解析成Document。
    } catch (Exception e) {
      throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    }
  }

這裡寫圖片描述

至此,XMLConfigBuilder的例項化操作已經完成。
接下來呼叫parse()方法,來初始化Configuration。

XMLConfigBuilder.java

  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
     //解析mybatis-config.xml檔案中的<configuration>下面的配置,並設定Configuration中的屬性。
    parseConfiguration(parser.evalNode("/configuration"));   //從根節點<configuration>開始解析配置
    return configuration;
  }

  //重點:在這裡,通過配置檔案中的配置,進一步對configuration例項進行各項配置。
  //這裡的解析順序就是xml DTDs中約定的順序。
  private void parseConfiguration(XNode root) {
    try {
      //XMLMapper的解析在settingsAsPropertiess()方法中呼叫了
      //具體分析見下面的Mapper.xml解析過程的分析
      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"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);    //對Configuration做設定
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));      // configuration.addMappers()註冊Mapper對映檔案
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

可以看到XPathParser#evalNode("/configuration")拿到了config.xml檔案的根節點,然後呼叫private void parseConfiguration(XNode root)開始解析XML檔案,
對應的配置檔案為:

  <configuration>
    <typeAliases>
        <typeAlias alias="User" type="com.cumt.mybatisstudy.entity.User"/>
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis_study"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

2.Mapper.xml內容的解析

XMLConfigBuilder.java

  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);

            //這裡將UserMapper.xml讀取為InputStream 
            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());
            //呼叫XMLMapperBuilder的parse()方法,由於在一行的XMLMapperBuilder例項化中已經將Configuration傳遞給XMLMapperBuilder,所以parse()解析的所有Mapper配置都會記錄到Configuration中。
            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.");
          }
        }
      }
    }

=============   Mapper.xml對映檔案的解析就在這裡啦=======================
XMLMapperBuilder.java

  public void parse() {
    //首先會判斷mapper對映檔案是否已經載入過
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
  }


  private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      //設定NameSpace。這裡用到了MapperBuilderAssistant類的幫助
      builderAssistant.setCurrentNamespace(namespace);

      //這兩個cache標籤沒用過,暫時不分析
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));

    //這裡開始解析對映檔案中的parameterMap、resultMap、以及sql語句。
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }
/**
* 在這裡沒有隻分析最常用的resultMap的註冊,其他的類似。
* sql 標籤也比較常用,解析過程與resultMap類似。在這裡不做追蹤。
* buildStatementFromContext()值得研究,這裡只認識CRUD基本操作的標籤,而且解析後會註冊給Configuration,在後面的Executor會拿這個資訊,根據[select|insert|update|delete]型別的不同調用不同的方法去執行sql和包裝ResultSet。
*/


XMLConfigBuilder.java

  private void resultMapElements(List<XNode> list) throws Exception {
    for (XNode resultMapNode : list) {
      try {
        resultMapElement(resultMapNode); //呼叫
      } catch (IncompleteElementException e) {
        // ignore, it will be retried
      }
    }
  }

XMLMapperBuilder.java

  private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
    return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
  }

//在這裡親切的看到了返回型別是ResultMap 
  public ResultMap addResultMap(
      String id,
      Class<?> type,
      String extend,
      Discriminator discriminator,
      List<ResultMapping> resultMappings,
      Boolean autoMapping) {
    id = applyCurrentNamespace(id, false);
    extend = applyCurrentNamespace(extend, true);

    if (extend != null) {
      if (!configuration.hasResultMap(extend)) {
        throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
      }
      ResultMap resultMap = configuration.getResultMap(extend);
      List<ResultMapping> extendedResultMappings = new ArrayList<ResultMapping>(resultMap.getResultMappings());
      extendedResultMappings.removeAll(resultMappings);
      // Remove parent constructor if this resultMap declares a constructor.
      boolean declaresConstructor = false;
      for (ResultMapping resultMapping : resultMappings) {
        if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
          declaresConstructor = true;
          break;
        }
      }
      if (declaresConstructor) {
        Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
        while (extendedResultMappingsIter.hasNext()) {
          if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
            extendedResultMappingsIter.remove();
          }
        }
      }
      resultMappings.addAll(extendedResultMappings);
    }

    //就是這裡建立了ResultMap
    ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
        .discriminator(discriminator)
        .build();

    //將ResultMap 註冊給萬能的configuration 2333
    configuration.addResultMap(resultMap);
    return resultMap;
  }

呼叫棧資訊如下圖:
這裡寫圖片描述

ResultMap.java

    public ResultMap build() {
      if (resultMap.id == null) {
        throw new IllegalArgumentException("ResultMaps must have an id");
      }
      resultMap.mappedColumns = new HashSet<String>();
      resultMap.idResultMappings = new ArrayList<ResultMapping>();
      resultMap.constructorResultMappings = new ArrayList<ResultMapping>();
      resultMap.propertyResultMappings = new ArrayList<ResultMapping>();
      for (ResultMapping resultMapping : resultMap.resultMappings) {
        resultMap.hasNestedQueries = resultMap.hasNestedQueries || resultMapping.getNestedQueryId() != null;
        resultMap.hasNestedResultMaps = resultMap.hasNestedResultMaps || (resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null);
        final String column = resultMapping.getColumn();
        if (column != null) {
          resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));
        } else if (resultMapping.isCompositeResult()) {
          for (ResultMapping compositeResultMapping : resultMapping.getComposites()) {
            final String compositeColumn = compositeResultMapping.getColumn();
            if (compositeColumn != null) {
              resultMap.mappedColumns.add(compositeColumn.toUpperCase(Locale.ENGLISH));
            }
          }
        }
        if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
          resultMap.constructorResultMappings.add(resultMapping);
        } else {
          resultMap.propertyResultMappings.add(resultMapping);
        }
        if (resultMapping.getFlags().contains(ResultFlag.ID)) {
          resultMap.idResultMappings.add(resultMapping);
        }
      }
      if (resultMap.idResultMappings.isEmpty()) {
        resultMap.idResultMappings.addAll(resultMap.resultMappings);
      }
      // lock down collections 設定這些Mapping為只讀的。java集合中的內容
      resultMap.resultMappings = Collections.unmodifiableList(resultMap.resultMappings);
      resultMap.idResultMappings = Collections.unmodifiableList(resultMap.idResultMappings);
      resultMap.constructorResultMappings = Collections.unmodifiableList(resultMap.constructorResultMappings);
      resultMap.propertyResultMappings = Collections.unmodifiableList(resultMap.propertyResultMappings);
      resultMap.mappedColumns = Collections.unmodifiableSet(resultMap.mappedColumns);
      return resultMap;
    }
  }

在來看XMLMapperBuilder#parse()方法中的bindMapperForNamespace();方法:

XMLMapperBuilder.xml

  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);
          //這裡是呼叫MapperRegistry新增Mapper,其實就是向map裡新增資料
          configuration.addMapper(boundType);
        }
      }
    }
  }

這回該看這3個方法了。
//TODO 這裡先打標記,後面搞明白這裡的用意再寫。

  • parsePendingResultMaps();
  • parsePendingChacheRefs();
  • parsePendingStatements();

至此, XMLConfigBuilder#mapperElement(XNode parent)方法呼叫返回, XMLConfigBuilder#parseConfiguration(XNode root)的呼叫也結束並返回。呼叫棧資訊如下圖:
這裡寫圖片描述

2.總結

config.xml和mapper.xml的載入和解析主要油XMLConfigBuilder和XMLMapperBuilder兩個類中的對應方法完成。最終解析的所有內容都會註冊到我們King類——Configuration中,這些資訊在Executor中以及ResultSet結果集wrapper中會用到。