MyBatis 3.5.4源码之旅三之mapper映射文件解析一

本文深入解析MyBatis3.5.4版本中mapper映射文件的处理流程,涵盖XMLConfigBuilder的mapper元素解析、package节点处理、MapperAnnotationBuilder的接口解析及XMLMapperBuilder的映射XML解析。重点介绍了不同资源加载方式、接口注册过程及CRUD语句解析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

对照的流程图

在这里插入图片描述

XMLConfigBuilder的mapper映射文件解析一

我们的映射配置解析就是在这里做的,包含了4种情况,其实都差不多的:

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.");
          }
        }
      }
    }
  }

package结点解析

package为例子:

<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="com.ww.dao"/>
</mappers>

直接调用configuration.addMappers(mapperPackage);最终会调用到MapperRegistryaddMappers

 public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
      addMapper(mapperClass);
    }
  }

在这里插入图片描述
这个代码很熟悉吧,跟前面别名的获取类似,只是最后是addMapper(mapperClass);。我们来看看怎么做的,其实处理的都是接口:

  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {//是接口类型
      if (hasMapper(type)) {//存在的话报异常
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
      //创建一个代理工厂,加入到knownMappers
        knownMappers.put(type, new MapperProxyFactory<>(type));
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();//解析
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

我们可以看到MapperProxyFactory里面其实就是存了接口Class对象:
在这里插入图片描述

MapperAnnotationBuilder的parse解析接口类对应的xml文件

然后用MapperAnnotationBuilder进行解析parser.parse();

public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {//没加载的话
    //加载映射文件
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      //添加缓存
      parseCache();
      parseCacheRef();
      Method[] methods = type.getMethods();//遍历所有方法解析
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }

其中有个loadXmlResource();方法,告诉我们为什么要把xml映射文件放在同包下且要同名了,其实就是把.换成可/最后加.xml,请看:

 private void loadXmlResource() {
    // Spring may not know the real resource name so we check a flag
    // to prevent loading again a resource twice
    // this flag is set at XMLMapperBuilder#bindMapperForNamespace
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
    //同包下且要同名,把.换成可/最后加.xml
      String xmlResource = type.getName().replace('.', '/') + ".xml";
      // #1347
      InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
      if (inputStream == null) {
        // Search XML mapper that is not in the module but in the classpath.
        try {
          inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
        } catch (IOException e2) {
          // ignore, resource is not required
        }
      }
      if (inputStream != null) {//如果能找到就用XMLMapperBuilder 解析
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        xmlParser.parse();
      }
    }
  }

在这里插入图片描述
原来他是通过替换分隔符然后加后缀名来表示xml映射文件路径的,然后创建XMLMapperBuilder去解析:

XMLMapperBuilder的parse解析映射xml

  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

然后先看configurationElement这个就是在解析映射文件了,具体都有对应的标签,一般的就不多说了:
在这里插入图片描述
其中比较重要的就是buildStatementFromContext(context.evalNodes("select|insert|update|delete"));,解析CRUD语句的:
在这里插入图片描述
这个还是比较复杂的,下次说吧。

好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值