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>

Configuration的实例化:详见Configuration的分析 mybatis源码学习——Configuration类及其初始化过程、TypeHandler、TypeAlias

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中会用到。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值