从源码角度看Mybatis如何解析mapper.xml

mapper.xml配置

mybatis-config.xml 配置文件中的<mappers>节点告诉 MyBatis去哪些位置查找映射配置文件。根据mybatis官网mapper有如下四种配置方式:
从源码角度看Mybatis如何解析mapper.xml_第1张图片
在mybatis源码中, XMLConfigBuilder.mapperElement(root.evalNode(“mappers”));负责解析节点,具体源码如下:

 private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
     // 遍历子节点进行解析处理
      for (XNode child : parent.getChildren()) {
        // 子节点是package
        if ("package".equals(child.getName())) {
          //获取package路径
          String mapperPackage = child.getStringAttribute("name");
          // 向Configuration的类成员变量MapperRegistry添加mapper接口
          configuration.addMappers(mapperPackage);
        } else {
          // 获取 resource ,url,mapperClass 
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            //设置 ErrorContext的resource
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // 创建XMLConfigBuilder对象
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            // 解析xml
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            //设置 ErrorContext的url
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
             // 创建XMLConfigBuilder对象
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            // 解析xml
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
           //设置 ErrorContext的mapperClass
            Class mapperInterface = Resources.classForName(mapperClass);
            // 向Configuration的类成员变量MapperRegistry添加mapper接口
            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文件解析整体逻辑

由上述源码可知,真正负责解析mapper.xml的是XMLMapperBuilder ,具体执行的方法时XMLMapperBuilder.parse(),该方法具体逻辑如下:

 public void parse() {
  // 判断是否已经加载过该xml文件。Configuration定义一个Set用来维护加载到配置文件中的xml文件:protected final Set loadedResources = new HashSet<>();
    if (!configuration.isResourceLoaded(resource)) {
     // 解析xml中的各种配置
      configurationElement(parser.evalNode("/mapper"));
      //解析完毕后向loadedResources 中添加mapper文件
      configuration.addLoadedResource(resource);
      //把 namespace(接口类型)和工厂类绑定起来,并向mapperRegistry添加该接口
      bindMapperForNamespace();
    }
     // 移除Configuration 中 解析失败的节点
    parsePendingResultMaps();
      // 移除Configuration 中 解析失败的节点
    parsePendingCacheRefs();
    // 移除Configuration 中 解析失败的XMLStatement即sql语句。
    parsePendingStatements();
  }

根据上述代码逻辑,mapper.xml解析过程主要分为如下两个方面:

1 mapper.xml各个节点解析

对mapper.xml各个节点解析的入口是configurationElement(parser.evalNode("/mapper")),该方法详细解析了xml中的各个节点和配置。源码如下:

// 解析mapper.xml中所以的子标签。
private void configurationElement(XNode context) {
    try {
      // 获取xml的namespace 
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      //记录当前命名空间
      builderAssistant.setCurrentNamespace(namespace);
      //对节点进行解析
      cacheRefElement(context.evalNode("cache-ref"));
      //对节点进行解析
      cacheElement(context.evalNode("cache"));
      //对节点进行解析,该节点目前已废弃
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
       //对节点进行解析
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      //对节点进行解析
      sqlElement(context.evalNodes("/mapper/sql"));
      //对增删改查四大节点进行解析,该方法最终结果是构建MappedStatement 对象
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

2 mapper接口绑定mapper.xml

在XMLMapperBuilder.bindMapperForNamespace()方法中,完成了映射配置文件与对应 Mapper 接口的绑定,具体实现如下:

private void bindMapperForNamespace() {
    // 获取当前命名空间namespace
    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) {
        // Configuration中是否已经加载过该接口
        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中添加该namespace
          configuration.addLoadedResource("namespace:" + namespace);
          //向Configuration中mapperRegistry注册该接口
          configuration.addMapper(boundType);
        }
      }
    }
  }

总结:通过对mapper.xml解析的源码分析,我们不难得出xml解析的过程,其实就是配置组装Configuration对象的过程,该对象里几乎包含了全部的配置信息,该对象贯穿于sql执行始末,在mybatis中举足轻重。

你可能感兴趣的:(Mybatis)