Mybatis初始化(一)


因为篇幅的原因,此部分只分析mybatis-config.xml配置文件的解析。


类似于SpringMybatis等灵活性和扩展性都很高的开源框架都提供了很多配置项,开发人员需要在使用时提供相应的配置信息,实现相应的需求。Mybatis中的配置文件主要有两个,分别是mybatis-config.xml配置文件和映射配置文件。

现在主流的配置方式除了使用XML配置文件,还会配合注解进行配置。在Mybatis初始化过程中,除了会读取mybatis-config.xml配置文件以及映射配置文件,还会加载配置文件指定的类,处理类中的注解,创建一些配置对象,最终完成框架中各个模块的初始化。另外,也可以使用Java API方式对Mybatis进行配置,这种硬编码的配置方式主要用在配置量比较少且配置信息不常变化的场景下。

1. 建造者模式

2. BaseBuidler

BaseBuilder的子类如下所示:

因为VelocitySqlSourceBuilder只是一个测试用例,所以我暂且不把它划为BaseBuilder,包括其静态内部类

ParameterMappingTokenHandler,这样BaseBuilder的子类结构图就如下所示:

正如签名所示,Mybatis的初始化过程使用了建造者模式,这里的BaseBuilder抽象类就扮演了建造者接口的角色。BaseBuilder中的核心字段的含义如下:

 /**
  * Configuration对象是Mybatis初始化过程中的核心对象,Mybatis中几乎所有配置信息都会保存到Configuration对象中。
  * 
   * Configuration对象是Mybatis初始化过程中创建的且是全局唯一的。
   * 也有人称它是一个“All in One” 对象
   */
  protected final Configuration configuration;
  /**
   * 在mybatis-config.xml配置文件中可以使用<typeAliases></typeAliases>标签定义别名,这些定义的别名都会记录在TypeAliasesRegistry对象中
   */
  protected final TypeAliasRegistry typeAliasRegistry;
  /**
   * 在mybatis-config.xml配置文件中可以使用<typeHandler></typeHandler>标签定义添加的自定义的TypeHandler,
   * 完成指定数据库类型与Java类型的转换,这些TypeHandler都会记录在TypeHandlerRegistry中
   */
  protected final TypeHandlerRegistry typeHandlerRegistry;

BaseBuilder中记录的TypeAliasRegistry对象和TypeHandlerRegistry对象,其实是全局唯一的,它们都是在Configuration对象初始化时创建的,代码如下所示:

BaseBuilder构造函数中,通过相应的Configuration.get*()方法得到TypeAliasRegistryTypeHandlerRegistry对象,并赋值给BaseBuilder相应的字段。

  /**
   * 构造方法
   * @param configuration
   */
  public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
  }

Configuration中还包含了很多配置项,为了便于读者理解,这里不会罗杰出内个字段的含义,而是在后面介绍的过程中,每涉及到一个配置项时,会结合其在Configuration中相应字段进行详细分析。

BaseBuilder.resolveAlias()方法依赖TypeAliasRegistry解析别名,BaseBuilder.resolveTypeHandler()方法依赖TypeHandlerRegistry查找指定的TypeHandler对象。在阅读完TypeAliasRegistryTypeHandlerRegistry的相关实现的介绍后,BaseBuilder.resolveAlias()BaseBuilder.resolveTypeHandler()就不难理解了。

前面提到过,Mybatis使用JdbcType枚举类型表示JDBC类型。Mybatis中常用的枚举类型还有ResultSetTypeParameterMode

  • ResultSetType枚举表示结果集类型;
  • ParameterMode枚举类型表示存储过程中的参数类型。

BaseBuilder中提供了相应的resolveJdbcType()resolveResultSetType()resolveParameterMode()方法,将String转换成对应的枚举类型,实现比较简单。

3. XMLConfigBuilder

XMLConfigBuilderBaseBuilder的众多子类之一,它扮演的是具体建造者的角色。XMLConfigBuilder主要负责解析mybatis-config.xml配置文件,其核心字段如下:

/**
   *   标识是否被解析过mybatis-config.xml
   */
  private boolean parsed;
  /**
   * 用于解析mybatis-config.xml配置文件的XPathParse对象,
   */
  private final XPathParser parser;
  /**
   * 标识<environment><environment/>配置的名称,默认读取<environment><environment/>标签的default属性
   */
  private String environment;
  /**
   * ReflectorFactory负责创建和缓存Reflector对象
   */
  private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();

XMLConfigBuilder.parse()方法是解析mybatis-config.xml配置文件的入口,它通过调用XMLConfigBuilder.parseConfiguration()方法实现整个解析过程,具体实现如下所示:

//XMLConfigBuilder.java
/**
   * 解析配置:
   *    这里其实有一个细节,就是{@link #parsed} 字段,这是一个boolbean类型的,也就是默认值是false,
   *    根据以下代码,发现只有当parsed = false才回去解析配置,为true的时候不会去解析,直接抛出异常,
   *    这么做的原因是:因为在解析mybatis中配置文件的时候是一件很消耗性能的事情,所以只解析一次。
   * @return configuration配置 (e.g. <configuration> ....<configuration/>)
   */
  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"));
    return configuration;
  }
//XMLConfigBuilder.java
/**
   * 解析Configuration标签中的配置
   * @param root
   */
  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      //解析<properties>节点
      propertiesElement(root.evalNode("properties"));
      // 解析<settings>节点
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      //设置vfsImpl字段
      loadCustomVfs(settings);
      //设置logImpl字段
      loadCustomLogImpl(settings);
      // 解析<typeAliases>节点
      typeAliasesElement(root.evalNode("typeAliases"));
      // 解析<plugins>节点
      pluginElement(root.evalNode("plugins"));
      // 解析<objectFactory>节点
      objectFactoryElement(root.evalNode("objectFactory"));
      // 解析<objectWrapperFactory>节点
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      // 解析<reflectorFactory>节点
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      //将settings值设置到Configuration中
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      // 解析<environments>节点
      environmentsElement(root.evalNode("environments"));
      // 解析<databaseIdProvider>节点
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      // 解析<typeHandlers>节点
      typeHandlerElement(root.evalNode("typeHandlers"));
      // 解析<mappers>节点
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

parseConfiguration()方法的代码还是比较整洁的,我们可以清楚地看到,XMLConfigBuildermybatis-config.xml配置文件中每一个节点的解析过程封装成一个相应的方法,本小节的后续内容将逐一分析这些节点的解析过程。

3.1. 解析<properties>节点

XMLConfigBuilder.propertiesElement()方法会解析mybatis-config.xml配置文件中的<properties>节点,并信形成java.uitl.Properties对象,之后将该Properties对象设置到XPathParserConfigurationvariable字段中。在后面的解析过程中,会使用该Properties对象中的信息替换占位符。propertiesElement()方法的具体实现如下所示:

/**
   * `XMLConfigBuilder.propertiesElement()`方法会解析`mybatis-config.xml`配置文件中的`<properties>`节点,
   * 并信形成`java.uitl.Properties`对象,之后将该`Properties`对象设置到`XPathParser`和`Configuration`的`variable`字段中。
   * 在后面的解析过程中,会使用该`Properties`对象中的信息替换占位符。
   * @param context  e.g. <properties resource="resources/config.properties">
   *                         <property name="username" value="xxx"/>
   *                         <property name="password" value="xxx"/>
   *                      </properties>
   * @throws Exception
   */
  private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
      // username = xxx 和password = xxx 两个
      Properties defaults = context.getChildrenAsProperties();
      String resource = context.getStringAttribute("resource");
      String url = context.getStringAttribute("url");
      //properties元素不能同时指定URL和基于资源的属性文件引用。请指定其中一个。
      if (resource != null && url != null) {
        throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
      }
      //这个其实就是使用外部化配置覆盖内部值
      if (resource != null) {
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
      Properties vars = configuration.getVariables();
      if (vars != null) {
        defaults.putAll(vars);
      }
      //更新XPathParser和Configuration的variable字段
      parser.setVariables(defaults);
      configuration.setVariables(defaults);
    }
  }

3.2. 解析<settings>节点

XMLConfigBuilder.settingsAsProperties()方法负责解析<settings>节点,在<settings>节点下的配置是Mybatis全局性的配置,它们会改变Mybatis的运行时行为,具体的配置项的含义请阅读参考Mybatis官方文档。需要注意的是,在Mybatis初始化时,这些全局配置信息都会被记录到Configuration对象的对应属性中。例如,开发人员可以通过设置————————。

Configuration中存在一个同名的相应字段,如下:

settingsAsProperties()方法的解析方式和propertiesElement()方法类似,但是多了使用MetaClass检测key指定的属性在Configuration类中是否有对应setter方法的步骤。settingsAsProperties()方法的代码如下所示:

  private Properties settingsAsProperties(XNode context) {
    if (context == null) {
      return new Properties();
    }
    Properties props = context.getChildrenAsProperties();
    // Check that all settings are known to the configuration class
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    //使用`MetaClass`检测`key`指定的属性在`Configuration`类中是否有对应`setter`方法的步骤
    for (Object key : props.keySet()) {
      if (!metaConfig.hasSetter(String.valueOf(key))) {
        throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
      }
    }
    return props;
  }

3.3. 设置vfsImpllogImpl

这两个参数一般不会去处理,所以我只罗列出来,不去解析:

3.3.1. 设置vfsImpl

参看VFS详解

  private void loadCustomVfs(Properties props) throws ClassNotFoundException {
    String value = props.getProperty("vfsImpl");
    if (value != null) {
      String[] clazzes = value.split(",");
      for (String clazz : clazzes) {
        if (!clazz.isEmpty()) {
          @SuppressWarnings("unchecked")
          Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
          configuration.setVfsImpl(vfsImpl);
        }
      }
    }
  }

3.3.2. 设置logImpl

  private void loadCustomLogImpl(Properties props) {
    Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
    configuration.setLogImpl(logImpl);
  }

3.4. 解析<typeAliases>节点

XMLConfigBuilder.typeAliasesElement()负责解析<typeAliases>节点及其子节点,然后利用TypeAliasRegistry完成别名的注册,具体实现如下所示:

private void typeAliasesElement(XNode parent) {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        /**
         * 指定报名,MyBatis 会在包名下面搜索需要的 Java Bean
         * 每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。
         * 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值 。
         */
        if ("package".equals(child.getName())) {
          String typeAliasPackage = child.getStringAttribute("name");
          configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
        } else {
          String alias = child.getStringAttribute("alias");
          String type = child.getStringAttribute("type");
          try {
            Class<?> clazz = Resources.classForName(type);
            if (alias == null) {
              typeAliasRegistry.registerAlias(clazz);
            } else {
              typeAliasRegistry.registerAlias(alias, clazz);
            }
          } catch (ClassNotFoundException e) {
            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
          }
        }
      }
    }
  }

3.5. 解析<plugins>节点

插件是Mybatis提供的扩展机制之一,用户可以通过添加自定义插件在SQL语句执行过程中的某一点进行拦截。Mybatis中的自定义插件只需要实现Interceptor接口,并通过注解指定想要拦截的方法签名即可。在高级主题模块,将详细介绍插件的使用和原理,这里先来分析Mybatis中如何加载和管理插件。

XMLConfigBuilder.pluginElement()方法负责解析<plugins>节点中定义的插件,并完成实例化和配置操作,具体实现如下所示:

 private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      //遍历全部的子节点,即遍历<plugins>
      for (XNode child : parent.getChildren()) {
        //获取<plugin>节点的interceptor属性的值
        String interceptor = child.getStringAttribute("interceptor");
        //获取<plugin>下<properties>配置的信息,并形成Properties对象
        Properties properties = child.getChildrenAsProperties();
        //通过别名获取对应的Clazz,然后通过反射实例化Interceptor对象
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
        //为当前interceptor设置属性值
        interceptorInstance.setProperties(properties);
        //记录Interceptor对象
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }

3.6. 解析<objectFactory><objectWrapperFactory><reflectorFactory>节点

我们可以通过自定义添加ObjectFactory、ObjectWarpperFactroy、ReflectorFactory的实现类来扩展Mybatis

XMLConfigBuilder.objectFactoryElement()方法负责解析并实例化<objectFactory>节点指定的ObjectFactory的实现类,之后将自定义的ObjectFactory对象记录在Configuration.objectFactory字段中,具体实现如下所示:


  private void objectFactoryElement(XNode context) throws Exception {
    if (context != null) {
      //获取<objectFactory>中的type属性
      String type = context.getStringAttribute("type");
      //获取<objectFactory>节点下的配置信息,并形成Properties对象
      Properties properties = context.getChildrenAsProperties();
      //通过别名获取对应的clazz,然后实例化ObjectFactory
      ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();
      //为实例化的ObjectFactory对象设置属性
      factory.setProperties(properties);
      //记录ObjectFactory对象
      configuration.setObjectFactory(factory);
    }
  }

XMLConfigBuilder对<objectWrapperFactory><reflectorFactory>节点的解析,和上述过程类似。

3.7. 将settings值设置到Configuration

XMLConfigBuilder.settingsElement()这个就是最简单的赋值操作,如下所示:

  private void settingsElement(Properties props) {
    configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
    configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
    configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
    configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
    configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
    configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
    configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
    configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
    configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
    configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
    configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
    configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
    configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
    configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
    configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
    configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
    configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
    configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
    configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
    configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
    configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
    configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
    configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
    configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
    configuration.setLogPrefix(props.getProperty("logPrefix"));
    configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
  }

3.8. 解析<environments>节点

在实际生产中,同一项目可能分为开发、测试和生产多个不同的环境,每个环境的配置可能不尽相同。Mybatis可以配置多个<environment>节点,每个<environment>节点对应一种环境的配置。但是需要注意的是,尽管可以配置多环境,每个SqlSessionFactory实例只能选择其一。

XMLConfigBuilder.environmentElement()方法负责解析<environment>的相关配置,它会根据XMLConfigBuilder.environment字段值确定要使用的<environment>配置,之后创建对应的TransactionFactoryDataSourceFactroy对象,通过DataSourceFactory.getDataSource()获取DataSource对象,并封装进Environment对象中。environmentElement()方法的具体实现如下:

 private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
      //未指定XMLConfigBuilder#environment字段的值,那么使用default属性指定的<environment></environment>
      if (environment == null) {
        environment = context.getStringAttribute("default");
      }
      for (XNode child : context.getChildren()) {
        String id = child.getStringAttribute("id");
        //检测字段与XMLConfigBuilder#environment字段是否匹配
        if (isSpecifiedEnvironment(id)) {
          //创建TransactionFactory:具体实现是先通过TypeAliasRegistry解析别名之后,实例化TransactionFactory
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          //创建DataSourceFactory然后得到DataSource
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          DataSource dataSource = dsFactory.getDataSource();
          //创建Environment对象,Environment中封装了上面创建的TransactionFactory和DataSource对象,这里应用的是建造者模式
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          //将Environment对象,记录在Configuration.environment字段中
          configuration.setEnvironment(environmentBuilder.build());
        }
      }
    }
  }

3.9. 解析<databaseIdProvider>节点

Mybatis不能像Hibernate那样使用hibernate.dialect进行设置,然后直接帮助开发人员屏蔽多种数据库产品在SQL语言支持方言的差异。但是可以在mybatis-config.xml配置文件中,通过<databaseIdProvider>定义所有支持的数据库产品的databaseId,然后在映射文件中定义SQL语句节点中,通过databaseId指定该SQL语句应用的数据库产品,这样也可以实现类似的功能。

Mybatis初始化时,会根据前面确定的DataSource确定当前使用的数据库产品,然后在解析映射配置文件时,加载不带databaseId属性和带有匹配当前数据库databaseId属性的所有SQL语句。如果同时找到了带databaseId和不带databaseId的相同语句,则后者会被舍弃,使用前者。

XMLConfigBuilder.databaseIdProviderElement()方法负责解析<databaseIdProvider>节点,并创建指定的DatabaseIdProvider对象。DatabaseIdProvider会返回一个databaseId值,Mybatis会根据databaseId选择合适的SQL去执行。

private void databaseIdProviderElement(XNode context) throws Exception {
    DatabaseIdProvider databaseIdProvider = null;
    if (context != null) {
      String type = context.getStringAttribute("type");
      // awful patch to keep backward compatibility
      //为了保证兼容性,修改type取值 <databaseIdProvider type="DB_VENDOR">
      if ("VENDOR".equals(type)) {
        type = "DB_VENDOR";
      }
      //解析相关配置信息
      Properties properties = context.getChildrenAsProperties();
      //创建DatabaseIdProvider对象
      databaseIdProvider = (DatabaseIdProvider) resolveClass(type).getDeclaredConstructor().newInstance();
      //配置DatabaseIdProvider,完成初始化
      databaseIdProvider.setProperties(properties);
    }
    Environment environment = configuration.getEnvironment();
    if (environment != null && databaseIdProvider != null) {
      //通过前面确定的DataSource获取databaseId,并记录到Configuration.databaseId字段中
      String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
      configuration.setDatabaseId(databaseId);
    }
  }

3.10. 解析<typeHandlers>节点

3.11. 解析<mappers>节点

Mybatis初始化时,除了加载mybatis-config.xml配置文件,还会加载全部的映射配置文件,mybatis-config.xml配置文件中的<mappers>节点会告诉我Mybatis去哪些位置查找映射配置文件以及使用了配置注解表示的接口。

XMLConfigBuilder.mapperElement()方法负责解析<mappers>节点,它会创建XMLMapperBuilder对象加载映射文件,如果映射配置文件存在相应的Mapper接口,也会加载相应的Mapper接口,解析其中的注解并完成向MapperRegistry的注册。

  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      //处理<mappers>的子节点
      for (XNode child : parent.getChildren()) {
        //处理<package>节点
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          //扫描指定的包,并向MapperRegistry注册Mapper接口
          configuration.addMappers(mapperPackage);
        } else {
          //获取<mapper>节点的resource、url、class属性,这三个属性是互斥的
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          //如果<mapper>节点指定了resource或者url属性,则创建XMLMapperBuilder对象,并通过该对象解析resource或url属性指定的Mapper配置文件
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            //创建XMLMapperBuilder对象,解析映射配置文件
            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对象,解析映射配置文件
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            //如果<mapper>节点指定了class属性,则向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.");
          }
        }
      }
    }
  }

Mybatis初始化过程中对mybatis-config.xml配置文件的解析过程到这里也就结束了,Mybatis初始化(二)会接续介绍,介绍Mybatis对映射文件的解析过程。



Mybatis源码分析   核心处理层      Mybatis源码分析

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!