mybatis源码分析之Mapper初始化过程(2)

3 篇文章 0 订阅
1 篇文章 0 订阅

在上一节记录了Mapper的初始化的整个过程,本节将记录一下Mapper的初始化过程中一个子过程:从xml解析Mapper配置。

Mapper的使用是首先定义一个mapper接口,在接口里面对mapper的入参和返回值进行定义,然后编写同名的mapper的xml配置文件,并在配置文件中对每一个接口的具体的sql的执行进行配置(当然使用注解也是一个很好的方式,在上一节提到了,注解的方式会覆盖掉xml的配置,但是在框架的初始化过程中首先是解析xml的,所以本节主要记录xml的解析主要做了什么工作)

在上一节提到了mapper的初始主要是在MapperAnnotationBuilder中的parse方法中完成的

public void parse() {
    // type是mapper的class信息
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      // 1、解析mapper的xml配置文件信息
      loadXmlResource();
      configuration.addLoadedResource(resource);
      // type.getName=xml文件中的mapper的namespace
      assistant.setCurrentNamespace(type.getName());
      // 2、解析mapper的cache注解
      parseCache();
      // 3、解析mapper中cache的引用
      parseCacheRef();
      // 4、解析mapper类型的所有的方法
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
              // 解析mapper接口的方法的注解信息
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }

本节主要介绍上述过程中的loadXmlResource();过程主要做了什么工作

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
    // 再次判断Mapper资源是否已经被加载
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
      // 把类的全限定名转换为对于的文件的路径
      String xmlResource = type.getName().replace('.', '/') + ".xml";
      InputStream inputStream = null;
      try {
        // 读取xml文件资源
        inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
      } catch (IOException e) {
        // ignore, resource is not required
      }
      if (inputStream != null) {
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        // 解析mapper对于的xml配置文件
        xmlParser.parse();
      }
    }
  }

从上面的代码可以看出,首先将类的权限定名转换为文件路径(其实类的全限定名也就是包路径加上文件名,如在com/mh目录下有一个文件test,那么该test文件的类的全限定名是com.mh.test,通过替换”.”为”/”就完成了全限定名到文件路径的转换,最后加上后缀”.xml”);然后将xml文件读取出来,最后完成xml的解析。所以如果需要了解mapper的初始化过程,需要深入到xml的解析中去。

XMLMapperBuilder.parse()

public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      // mapper的xml节点解析
      configurationElement(parser.evalNode("/mapper"));
      // 设置该mapper为已加载的资源
      configuration.addLoadedResource(resource);
      // 绑定Mapper的命名空间,后续的一些mapper信息的缓存需要使用该命令空间
      // 来区分不同的mapper的解析的内容。(这是由于所有mapper的解析结果会全部
      // 存放在Configuration中的同一个位置,所以需要命名空间来区分不同mapper的解析结果)
      bindMapperForNamespace();
    }

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

从上面的代码中我们可以看到,configurationElement(parser.evalNode(“/mapper”));函数是主要负责mapper节点的解析的,所以深入到该源码里面去看看
XMLMapperBuilder.configurationElement(XNode context)

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");
      }
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      /*
       * 解析<parameterMap></parameterMap>所有的节点
       * parameterMap – 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。
       */
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      // 解析所有resultMap的节点
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      // 解析所有的sql节点
      sqlElement(context.evalNodes("/mapper/sql"));
      // 解析所有的select|insert|update|delete等节点信息
      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);
    }
  }

从上面我们可以看出,会依次解析mapper下的一些子节点,我们选择其中一个子节点的解析过程看看解析的过程,我们通过分析resultMap的解析过程来分析具体的解析内容resultMapElements(context.evalNodes(“/mapper/resultMap”));

/**
   * 解析resultMap节点信息
   * @param list resultMap节点列表
   * @throws Exception
   */
  private void resultMapElements(List<XNode> list) throws Exception {
    for (XNode resultMapNode : list) {
      try {
        // resultMapNode是代表xml中的一个resultMap节点
        resultMapElement(resultMapNode);
      } catch (IncompleteElementException e) {
        // ignore, it will be retried
      }
    }
  }

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

  // 解析resultMap节点并生成ResultMap类
  private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    String id = resultMapNode.getStringAttribute("id",
        resultMapNode.getValueBasedIdentifier());
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    String extend = resultMapNode.getStringAttribute("extends");
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    Class<?> typeClass = resolveClass(type);
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
    resultMappings.addAll(additionalResultMappings);
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
      if ("constructor".equals(resultChild.getName())) {
        processConstructorElement(resultChild, typeClass, resultMappings);
      } else if ("discriminator".equals(resultChild.getName())) {
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
        List<ResultFlag> flags = new ArrayList<ResultFlag>();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      // 将解析结果封装成为ResultMap并添加到Configuration里面的resultMaps中
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

在上面的代码中我们看到最终会解析生成,具体如何添加到Configuration的resultMaps中的,可以继续查看resultMapResolver.resolve();方法即可看到。

通过上面的ResultMap的解析过程可以看出了mapper的xml的解析过程,xml的mapper的其他节点如:parameterMap、sql、select|insert|update|delete等节点的解析过程都是一样的,且最终都会被添加到Configuration中对应的map集合中去(问题来了,既然所有的mapper的xml的解析都会被添加到Configuration中去,那么如何区分不同的mapper的resultMap节点等信息呢,通过mapper的namespace,详情可以查看MapperBuilderAssistant.addResultMap方法)

MapperBuilderAssistant.addResultMap方法

public ResultMap addResultMap(
      String id,
      Class<?> type,
      String extend,
      Discriminator discriminator,
      List<ResultMapping> resultMappings,
      Boolean autoMapping) {
    // 为ID应用命名空间,用于区分不同Mapper的XML中相同的ID
    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 = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
        .discriminator(discriminator)
        .build();
    configuration.addResultMap(resultMap);
    return resultMap;
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值