加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

mybatis源码配置文件解析之五:解析mappers标签

发布时间:2020-12-14 18:03:29 所属栏目:大数据 来源:网络整理
导读:在上篇博客中分析了plugins标签,《mybatis源码配置文件解析之四:解析plugins标签?》,了解了其使用方式及背后的原理。现在来分析mappers标签。 一、概述 在mybatis的核心配置文件(mybatis-config.xml)中,有关mappers的配置如下, mappers !-- mapper re

在上篇博客中分析了plugins标签,《mybatis源码配置文件解析之四:解析plugins标签?》,了解了其使用方式及背后的原理。现在来分析<mappers>标签。

一、概述

在mybatis的核心配置文件(mybatis-config.xml)中,有关mappers的配置如下,

<mappers>
        <!-- <mapper resource="cn/com/mybatis/dao/UserMapper.xml"/> 
        <mapper resource="cn/com/mybatis/dao/MenuMapper.xml"/> -->
        第二种做法 package name="cn.com.mybatis.dao" />

    </>

从上面的配置文件,可以看到配置mappers文件有两种方式,一种是配置mapper标签,另一种是配置package标签。从配置的内容上来看,其配置的方式也是存在差别,配置mapper标签配置的是一个xml文件,该文件中存在相关的sql语句;配置package标签配置的是一个包的权限路径(在spring和mybatis结合的时候使用了此种方式),该包表示的是mapper的接口文件。

最终上面的两种方式都会被解析到mybatis的configuration类中,供用户使用。如果存在重复配置mybatis会如何处理,下面在分析过程中会解答该问题。

二、详述

上面了解了<mappers>标签的使用方式,下面看mybatis是如何解析该标签的。

在XMLConfigBuilder类中的parseConfiguration方法

private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      解析properties标签    
      propertiesElement(root.evalNode("properties"));
      解析settings标签,1、把<setting>标签解析为Properties对象
      Properties settings = settingsAsProperties(root.evalNode("settings"/*2、对<settings>标签中的<setting>标签中的内容进行解析,这里解析的是<setting name="vfsImpl" value=",">
      * VFS是mybatis中用来表示虚拟文件系统的一个抽象类,用来查找指定路径下的资源。上面的key为vfsImpl的value可以是VFS的具体实现,必须
      * 是权限类名,多个使用逗号隔开,如果存在则设置到configuration中的vfsImpl属性中,如果存在多个,则设置到configuration中的仅是最后一个
      * */
      loadCustomVfs(settings);
      解析别名标签,例<typeAlias alias="user" type="cn.com.bean.User"/>
      typeAliasesElement(root.evalNode("typeAliases"解析插件标签
      pluginElement(root.evalNode("plugins"解析objectFactory标签,此标签的作用是mybatis每次创建结果对象的新实例时都会使用ObjectFactory,如果不设置
      则默认使用DefaultObjectFactory来创建,设置之后使用设置的
      objectFactoryElement(root.evalNode("objectFactory"解析objectWrapperFactory标签
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"解析reflectorFactory标签
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
       read it after objectFactory and objectWrapperFactory issue #631
      解析environments标签
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"解析<mappers>标签
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e,e);
    }
  }

在该方法的最下方,看下面这行代码

解析<mappers>标签
      mapperElement(root.evalNode("mappers"));

上面这行代码便是解析mappers标签的方法的调用。看其方法定义,

/**
 * 解析<mappers>标签,在此标签中可以配置<mapper>和<package>两种标签,其中<mapper>标签可以配置resource、url、class三种属性,
 * 这里的三种属性,仅可以同时出现一个;<package>标签只需要配置包名即可。
 * @param parent
 * @throws Exception
 */
  void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
          1、解析package标签,获得name属性即包名
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          扫描包名,把
          configuration.addMappers(mapperPackage);
        } else {2、解析<mapper>标签,标签中可以配置resource、url、class三个属性,但只能配置其中一个。
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class"if (resource != null && url == null && mapperClass == ) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream,configuration,resource,configuration.getSqlFragments());
            
             * 处理mapper文件和对应的接口
             
            mapperParser.parse();
          } else if (resource == null && url != ) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = null && mapperClass != ) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            new BuilderException("A mapper element may only specify a url,resource or class,but not more than one.");
          }
        }
      }
    }
  }

通过上面代码的分析及在配置文件中的配置,解析<mappers>标签分为两部分,分别解析package和mapper子标签。且是循环解析,也就是在含义多个包的时候需要配置多个package子标签。

1、解析package子标签

从上面的方法也就是mapperElement方法中,可以知道在解析<mappers>标签时首先解析的是package子标签,也就是说在同时配置package和mapper子标签时,先解析的是package子标签,解析标签是有顺序的。下面解析package子标签的过程,仅给出和解析package有关的代码,

            configuration.addMappers(mapperPackage);
        }

上面的代码,解析出package子标签中的包名,调用了configuration.addMappers方法,

public  addMappers(String packageName) {
    mapperRegistry.addMappers(packageName);
  }

调用了mapperRegistry.addMappers方法,


   * @since 3.2.2
    addMappers(String packageName) {
    addMappers(packageName,Object.class);
  }

下面看addMappers方法,

void addMappers(String packageName,Class<?> superType) {
      解析packageName下的class文件
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find( ResolverUtil.IsA(superType),packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    处理解析好的mapper接口文件
    for (Class<?> mapperClass : mapperSet) {
      addMapper(mapperClass);
    }
  }

上面的方法首先会解析指定包下的class文件,看下面的解析过程,

resolverUtil.find(new ResolverUtil.IsA(superType),packageName);

看find方法,

public ResolverUtil<T> find(Test test,String packageName) {
      把包名中的“.”替换成“/”
    String path = getPackagePath(packageName);

     {
        获得包路径下的所有文件名称
      List<String> children = VFS.getInstance().list(path);
       (String child : children) {
        if (child.endsWith(".class")) {
          addIfMatching(test,child);
        }
      }
    }  (IOException ioe) {
      log.error("Could not read package: " + packageName,ioe);
    }

    return this;
  }

遍历包下的所有class文件,调用addIfMatching方法,

@SuppressWarnings("unchecked")
  protected  addIfMatching(Test test,String fqn) {
     {
      String externalName = fqn.substring(0,fqn.indexOf('.')).replace('/','.');
      ClassLoader loader = getClassLoader();
      if (log.isDebugEnabled()) {
        log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
      }

      Class<?> type = loader.loadClass(externalName);
       (test.matches(type)) {
        matches.add((Class<T>) type);
      }
    }  (Throwable t) {
      log.warn("Could not examine class '" + fqn + "'" + " due to a " +
          t.getClass().getName() + " with message: " + t.getMessage());
    }
  }

加载class文件,判断是否符合test.matches,该方法如下,

 Returns true if type is assignable to the parent type supplied in the constructor. 
    @Override
    boolean matches(Class<?> type) {
      return type != null && parent.isAssignableFrom(type);
    }

如果符合条件则放入matches中,matches定义在ResolverUtil中。回到addMappers方法中,find方法结束后调用下面的方法,获取matches中的值,

Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();

然后循环解析mapperSet,

 mapperClass : mapperSet) {
      addMapper(mapperClass);
    }

解析过程如下,

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {判断是否为接口
      if (hasMapper(type)) {如果knownMappers中已经存在该type,则抛出异常
        new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
       {
          把type放入knownMappers中,其value为一个MapperProxyFactory对象
        knownMappers.put(type,new MapperProxyFactory<T>(type));
         It's important that the type is added before the parser is run
         otherwise the binding may automatically be attempted by the
         mapper parser. If the type is already known,it won't try.
        对mapper文件进行解析,
        MapperAnnotationBuilder parser =  MapperAnnotationBuilder(config,type);
        具体的解析过程,1、先解析对应的XML映射文件,2、再解析接口方法中的注解信息
        parser.parse();
        loadCompleted = true;
      } finallyif (!loadCompleted) {如果解析失败,则删除knowMapper中的信息
          knownMappers.remove(type);
        }
      }
    }
  }

把mapper接口类封装为MapperProxyFactory对象,并放入knownMappers中,接着对接口类进行解析,如果解析失败会把刚才放入knownMappers中的值从knownMappers中移除。下面看如何解析接口类(解析对应的XML文件),

 parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
        解析和接口同名的xml文件,前提是存在该文件,如果不存在该文件要怎么解析那?答案是解析接口中方法上的注解
        
         * 解析和接口同名的xml配置文件,最终要做的是把xml文件中的标签,转化为mapperStatement,
         * 并放入mappedStatements中
         * 
         
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      解析接口上的@CacheNamespace注解
      parseCache();
      parseCacheRef();
      获得接口中的所有方法,并解析方法上的注解
      Method[] methods = type.getMethods();
       (Method method : methods) {
         issue #237
          method.isBridge()) {
              解析方法上的注解
            parseStatement(method);
          }
        }  (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(,method));
        }
      }
    }
    parsePendingMethods();
  }

上面的解析分为两个过程,首先解析对应的XML映射文件,再解析方法上的注解。

1.1、解析xml文件

下面看如何继续对应的XML文件,

loadXmlResource();

看如何解析xml文件,

 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映射文件,其名称为接口类+"."+xml,即和接口类同名且在同一个包下。
      String xmlResource = type.getName().replace('.','/') + ".xml";
      InputStream inputStream =  {
        inputStream = Resources.getResourceAsStream(type.getClassLoader(),xmlResource);
      }  (IOException e) {
         ignore,resource is not required
      }
      if (inputStream != ) {
        XMLMapperBuilder xmlParser = 解析xml映射文件
        xmlParser.parse();
      }
    }
  }

首先确定XML映射文件的位置,和接口类同名且在同一个包下。如下的例子,

确定好对应的映射文件位置,接着便是解析该xml文件,

        xmlParser.parse();
      }

解析过程如下,

 parse() {
    解析mapper文件中的<mapper>标签及其子标签
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

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

解析的过程在解析<mapper>标签的时候再详细分析。解析的最终结果是把XML中的select|update|delete|insert标签转化为MappedStatement对象,放入configuration中。

1.2、解析接口中方法上的注解

上面解析了接口对于的XML文件,下面看如何解析接口中的方法,


 parseStatement(Method method) {
    Class<?> parameterTypeClass = getParameterType(method);
    LanguageDriver languageDriver = getLanguageDriver(method);
    获得方法上的注解,并生成SqlSource
    SqlSource sqlSource = getSqlSourceFromAnnotations(method,parameterTypeClass,languageDriver);
    if (sqlSource != ) {
      Options options = method.getAnnotation(Options.);
      生成mappedStatementId,为接口的权限类名+方法名。从这里可以得出同一个接口或namespace中不允许有同名的方法名或id
      final String mappedStatementId = type.getName() + "." + method.getName();
      Integer fetchSize = ;
      Integer timeout = ;
      StatementType statementType = StatementType.PREPARED;
      ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
      SqlCommandType sqlCommandType = getSqlCommandType(method);
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
      boolean flushCache = !isSelect;
      boolean useCache = isSelect;

      KeyGenerator keyGenerator;
      String keyProperty = "id";
      String keyColumn = if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
         first check for SelectKey annotation - that overrides everything else
        SelectKey selectKey = method.getAnnotation(SelectKey.);
        if (selectKey != ) {
          keyGenerator = handleSelectKeyAnnotation(selectKey,mappedStatementId,getParameterType(method),languageDriver);
          keyProperty = selectKey.keyProperty();
        } if (options == ) {
          keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        }  {
          keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
          keyProperty = options.keyProperty();
          keyColumn = options.keyColumn();
        }
      }  {
        keyGenerator = NoKeyGenerator.INSTANCE;
      }

      if (options != ) {
         (FlushCachePolicy.TRUE.equals(options.flushCache())) {
          flushCache = ;
        }  (FlushCachePolicy.FALSE.equals(options.flushCache())) {
          flushCache = ;
        }
        useCache = options.useCache();
        fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; issue #348
        timeout = options.timeout() > -1 ? options.timeout() : ;
        statementType = options.statementType();
        resultSetType = options.resultSetType();
      }

      String resultMapId = ;
      ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.if (resultMapAnnotation != ) {
        String[] resultMaps = resultMapAnnotation.value();
        StringBuilder sb =  StringBuilder();
         (String resultMap : resultMaps) {
          if (sb.length() > 0) {
            sb.append(",");
          }
          sb.append(resultMap);
        }
        resultMapId = sb.toString();
      }  (isSelect) {
        resultMapId = parseResultMap(method);
      }

      assistant.addMappedStatement(
          mappedStatementId,sqlSource,statementType,sqlCommandType,fetchSize,timeout, ParameterMapID
           TODO gcode issue #577
           DatabaseID
           ResultSets
          options != null ? nullOrEmpty(options.resultSets()) : );
    }
  }

从上面的代码,可以看出最终调用了assistant.addMappedStatement方法,该方法会把注解信息封装为MappedStatement对象,放入configuration中。详细过程,后面分析。

2、解析mapper子标签

上面分析了mybatis解析<package>标签的过程,下面看直接解析<mapper>子标签。代码为部分代码

);
          }
        }

前边说过,在<mapper>子标签中可以配置resource、url、class三个属性,但是只能配置其中一个,上面分别对其进行了解析,其解析过程和上面解析<packge>中的过程类似,解析resource和url属性的时候都是把XML映射文件解析为inputSream,然后对文件进行解析;解析class属性的时候和解析<package>的过程一样。

三、总结

本文分析了mybatis解析<mappers>标签的过程,分为解析<package>、<mapper>子标签,其解析过程主要为解析Mapper接口和XML映射文件,其详细过程后面详细分析。

?

有不当之处,欢迎指正,感谢!

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读