Mybatis解析mybatis-config.xml过程

文章目录

  • 1. 相关代码
  • 2. 加载资源
  • 3. 创建SqlSessionFactory
    • 3.1 整体创建过程
    • 3.2. 创建XMLConfigBuilder.
      • 3.2.1 XMLConfigBuilder 的核心字段
      • 3.2.2 BaseBuilder
    • 3.3 创建Configuration
    • 3.4 全局配置文件解析过程
      • 3.4.1 parseConfiguration方法
      • 3.4.2 properties标签解析
      • 3.4.3 settings标签解析
      • 3.4.4 typeAliases解析
      • 3.4.5 plugins解析
      • 3.4.6 objectFactory,objectWrapperFactory及reflectorFactory解析
      • 3.4.7 settings 子标签赋值
      • 3.4.8 environments标签
      • 3.4.9 databaseIdProvider
      • 3.4.10 typeHandlers标签解析
      • 3.4.11 mapper解析
    • 3.5 映射文件解析
      • 3.5.1 configurationElement()——解析所有的子标签,最终获得MappedStatement对象。
      • 3.5.2 bindMapperForNamespace(); // 注册 Mapper 接口
  • 4. 总结

1. 相关代码

@Test
    public void test2() throws Exception{
        // 1.获取配置文件
        InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
        // 2.加载解析配置文件并获取SqlSessionFactory对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        // 3.根据SqlSessionFactory对象获取SqlSession对象
        SqlSession sqlSession = factory.openSession();
        // 4.通过SqlSession中提供的 API方法来操作数据库
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.selectUserById(1);
        System.out.println(user);
        // 5.关闭会话
        sqlSession.close();
    }

2. 加载资源

Mybatis有自己的资源加载工具Resources,从classpath加载mybatis-config.xml配置文件。

3. 创建SqlSessionFactory

3.1 整体创建过程

SqlSessionFactoryBuilder从名称末尾的Builder可知,这是一个运用建造者模式的类,专门用来创建SqlSessionFactory。SqlSessionFactoryBuilder只是用来创建SqlSessionFactory,完成创建后就可以销毁。

进入build(in)方法查看:

 public SqlSessionFactory build(InputStream inputStream) {
    // test 6666 --> 999 0000
    return build(inputStream, null, null);
  }

这里重载了build方法:


  /**
   * 1.创建了一个XMLConfigBuilder对象 会完成很多初始化操作 最终的是创建了 Configuration 对象
   * 2.parser.parse() 完成全局配置文件的加载解析 并将相关的信息封装到 Configuration 对象中
   *                  同时会完成映射文件(UserMapper.xml)的加载解析,相关的信息同样的会被保存到 Configuration对象中
   *                  select/insert/udpate/delete 标签的信息会被封装到 MapperdStatement对象中
   * 3.build(parser.parse()) ==》直接创建了 DefaultSqlSessionFactory对象
   *
   *
   * 1.创建了一个解析器
   * 2.通过解析器解析全局配置文件
   * 3.通过得到的Configuration对象来创建DefaultSqlSessionFactory实例
   * @param inputStream
   * @param environment
   * @param properties
   * @return
   */
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      // 用于解析 mybatis-config.xml,同时创建了 Configuration 对象 >> ok  完成了很多的初始化操作
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      // 解析XML,最终返回一个 DefaultSqlSessionFactory >>
      //  全局配置文件中的信息都被封装到了 Configuration对象中
      //  映射文件中的 配置信息 同样的也被封装到了 Configuration 对象中
      //  一个具体的 CRUD 标签的信息 被封装到 MappedStatment 对象中
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

总结:XMLConfigBuilder对象完成全局配置文件和映射文件的解析,得到Configuration对象,Configuration作为入参创建DefaultSqlSessionFactory对象。

3.2. 创建XMLConfigBuilder.

从XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties)按F7进入。

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    // EntityResolver的实现类是XMLMapperEntityResolver 来完成配置文件的校验,根据对应的DTD文件来实现
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }

然后重载构造函数,这个函数是private,非常有意思的设计,其他构造函数最终都是调用这个函数。

 private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration()); // 完成了Configuration的初始化  类型别名的注册
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props); // 设置对应的Properties属性
    this.parsed = false; // 设置 是否解析的标志为 false
    this.environment = environment; // 初始化environment
    this.parser = parser; // 初始化 解析器
  }

3.2.1 XMLConfigBuilder 的核心字段

 // 标识是否解析过mybatis-config.xml文件
  private boolean parsed;
  // 用于解析mybatis-config.xml 配置文件的 XPathParser对象
  private final XPathParser parser;
  // 标签定义的环境名称
  private String environment;
  // 心功能是实现对 Reflector 对象的创建和缓存
  private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();

3.2.2 BaseBuilder

super(new Configuration())

XMLConfigBuilder继承了抽象类BaseBuilder,这里super就是调用父类的构造函数。F7跟入:

 // Configuration 是MyBatis初始化过程的核心对象,MyBatis中的几乎全部配置信息会保存在 Configuration 对象中
  protected final Configuration configuration;
  // 别名的注册器
  protected final TypeAliasRegistry typeAliasRegistry;
  // 类型处理器的注册器
  protected final TypeHandlerRegistry typeHandlerRegistry;

  public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
  }

BaseBuilder持有三个成员变量:

  • configuration,配置核心对象,MyBatis 的初始化过程就是围绕 Configuration 对象展开的,我们可以认为 Configuration 是一个单例对象,MyBatis 初始化解析到的全部配置信息都会记录到 Configuration 对象中。
  • typeAliasRegistry,别名注册器
  • typeHandlerRegistry,类型注册器。

3.3 创建Configuration

    super(new Configuration()); // 完成了Configuration的初始化  类型别名的注册

Configuration持有很多变量,其创建过程值得探究。

3.4 全局配置文件解析过程

parser.parse()

  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // XPathParser,dom 和 SAX 都有用到 >>
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

3.4.1 parseConfiguration方法


  /**
   * 全局配置文件中的配置信息都被加载到 Configuration 对象中
   * @param root
   */
  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      // 对于全局配置文件各种标签的解析   都被保存到了 Configuration 对象中。
      propertiesElement(root.evalNode("properties"));
      // 解析 settings 标签  返回的结果就是 settings 中定义的信息  自定义的属性
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      // 读取文件
      loadCustomVfs(settings);
      // 日志设置
      loadCustomLogImpl(settings);
      // 类型别名
      typeAliasesElement(root.evalNode("typeAliases"));
      // 插件
      pluginElement(root.evalNode("plugins"));
      // 用于创建对象
      objectFactoryElement(root.evalNode("objectFactory"));
      // 用于对对象进行加工
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      // 反射工具箱
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      // settings 子标签赋值,默认值就是在这里提供的 >>  加载默认设置
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      // 创建了数据源 >>
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      // 自定义的类型处理器的解析
      typeHandlerElement(root.evalNode("typeHandlers"));
      // 解析引用的Mapper映射器 ===》 映射文件的加载解析
      mapperElement(root.evalNode("mappers")); // 映射文件中的信息 加载解析出来后保存到了哪个对象中?
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

安装顺序解析标签:

  • properties
  • settings
  • typeAliases
  • typeHandlers
  • objectFactory
  • plugins
  • environments
    • environment
      • transactionManager
      • dataSource
  • databaseIdProvider
  • mappers

3.4.2 properties标签解析

  /**
   * 加载解析 properties 标签  并且将相关的属性信息更新保存到了 Configuration 对象中了
   * @param context
   * @throws Exception
   */
  private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
      // 创建了一个 Properties 对象,后面可以用到
      Properties defaults = context.getChildrenAsProperties();
      String resource = context.getStringAttribute("resource");
      String url = context.getStringAttribute("url");
      if (resource != null && url != null) {
        // url 和 resource 不能同时存在
        throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
      }
      // 加载resource或者url属性中指定的 properties 文件
      if (resource != null) {
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
      // 配置文件中 已经有的 属性信息
      Properties vars = configuration.getVariables();
      if (vars != null) {
        // 和 Configuration中的 variables 属性合并
        defaults.putAll(vars);
      }
      // 更新对应的属性信息
      parser.setVariables(defaults);
      // 解析出来的 属性信息 被保存到了 configuration 对象中
      configuration.setVariables(defaults);
    }
  }

3.4.3 settings标签解析


  /**
   * 将 sttings 标签中配置的 信息封装到了 Properties 对象中,并且返回了
   * @param context
   * @return
   */
  private Properties settingsAsProperties(XNode context) {
    if (context == null) {
      return new Properties();
    }
    // 获取settings节点下的所有的子节点
    Properties props = context.getChildrenAsProperties();
    // Check that all settings are known to the configuration class  用户到了 反射工具箱 中的内容 实现反射处理
    // 创建Configuration对应的MetaClass对象
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    for (Object key : props.keySet()) {
      // 检验配置的拼写是否准确,是否有setter方法
      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;
  }
  • 读取文件
 // loadCustomVfs是获取Vitual File System的自定义实现类,比如要读取本地文件,
 // 或者FTP远程文件的时候,就可以用到自定义的VFS类。 (用到的机会很少)
 loadCustomVfs(settings);
  • 日志设置
loadCustomLogImpl(settings);
 private void loadCustomLogImpl(Properties props) {
    // 获取 logImpl设置的 日志 类型
    Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
    // 设置日志  这块代码是我们后面分析 日志 模块的 关键代码
    configuration.setLogImpl(logImpl);
  }

3.4.4 typeAliases解析

typeAliasesElement(root.evalNode("typeAliases"));
private void typeAliasesElement(XNode parent) {
    // 放入 TypeAliasRegistry
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
      	// 解析包下面的类,默认别名的类名小写,如果类上有@Alias,就取注释的value
        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) {
              // 扫描 @Alias 注解使用
              typeAliasRegistry.registerAlias(clazz);
            } else {
              // 直接注册
              typeAliasRegistry.registerAlias(alias, clazz);
            }
          } catch (ClassNotFoundException e) {
            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
          }
        }
      }
    }
  }

3.4.5 plugins解析

pluginElement(root.evalNode("plugins"));
private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        // 获取 节点的 interceptor 属性的值
        String interceptor = child.getStringAttribute("interceptor");
        // 获取 下的所有的properties子节点
        Properties properties = child.getChildrenAsProperties();
        // 初始化 Interceptor 对象
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
        // 设置 interceptor的 属性
        interceptorInstance.setProperties(properties);
        //  将Interceptor对象添加到Configuration的插件链中保存,等待后续使用
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }

3.4.6 objectFactory,objectWrapperFactory及reflectorFactory解析

  • objectFactory
    加载自定义的ObjectFactory。ObjectFactory用来创建返回的对象。
private void objectFactoryElement(XNode context) throws Exception {
    if (context != null) {
      // 获取 节点的 type 属性
      String type = context.getStringAttribute("type");
      // 获取  节点下的配置信息
      Properties properties = context.getChildrenAsProperties();
      // 获取ObjectFactory 对象的对象 通过反射方式
      ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();
      // ObjectFactory 和 对应的属性信息关联
      factory.setProperties(properties);
      // 将创建的ObjectFactory对象绑定到Configuration中
      configuration.setObjectFactory(factory);
    }
  }
  • objectWrapperFactory
    ObjectWrapperFactory用来对对象做特殊的处理。比如:select没有写别名,查询返回的是一个Map,可以在自定义的objectWrapperFactory中把下划线命名变成驼峰命名。
private void objectWrapperFactoryElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type");
      ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).getDeclaredConstructor().newInstance();
      configuration.setObjectWrapperFactory(factory);
    }
  }
  • reflectorFactory
    ReflectorFactory是反射的工具箱,对反射的操作进行了封装。
  private void reflectorFactoryElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type");
      ReflectorFactory factory = (ReflectorFactory) resolveClass(type).getDeclaredConstructor().newInstance();
      configuration.setReflectorFactory(factory);
    }
  }

3.4.7 settings 子标签赋值

// settings 中的属性信息都是直接保存在 Configuration 对象的属性中的
  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.4.8 environments标签

private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
      if (environment == null) {
        environment = context.getStringAttribute("default");
      }
      for (XNode child : context.getChildren()) {
        String id = child.getStringAttribute("id");
        if (isSpecifiedEnvironment(id)) {
          // 事务工厂
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          // 数据源工厂(例如 DruidDataSourceFactory )
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          // 数据源
          DataSource dataSource = dsFactory.getDataSource();
          // 包含了 事务工厂和数据源的 Environment
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          // 放入 Configuration   Environment 存储了 DataSourceFactory TransactionFactory
          configuration.setEnvironment(environmentBuilder.build());
        }
      }
    }
  }

3.4.9 databaseIdProvider

解析databaseIdProvider标签,生成DatabaseIdProvider对象(用来支持不同厂商的数据库)。

在 MyBatis 中编写的都是原生的 SQL 语句,而很多数据库产品都会有一些 SQL 方言,这些方言与标准 SQL 不兼容。

在 mybatis-config.xml 配置文件中,我们可以通过 标签定义需要支持的全部数据库的 DatabaseId,在后续编写 Mapper 映射配置文件的时候,就可以为同一个业务场景定义不同的 SQL 语句(带有不同的 DataSourceId),来支持不同的数据库,这里就是靠 DatabaseId 来确定哪个 SQL 语句支持哪个数据库的。

3.4.10 typeHandlers标签解析

 typeHandlerElement(root.evalNode("typeHandlers"));
private void typeHandlerElement(XNode parent) {

    if (parent != null) {

        for (XNode child : parent.getChildren()) { // 处理全部子标签

            if ("package".equals(child.getName())) { 

                // 如果指定了package属性,则扫描指定包中所有的类,

                // 并解析@MappedTypes注解,完成TypeHandler的注册

                String typeHandlerPackage = child.getStringAttribute("name");

                typeHandlerRegistry.register(typeHandlerPackage);

            } else {

                // 如果没有指定package属性,则尝试获取javaType、jdbcType、handler三个属性

                String javaTypeName = child.getStringAttribute("javaType");

                String jdbcTypeName = child.getStringAttribute("jdbcType");

                String handlerTypeName = child.getStringAttribute("handler");

                // 根据属性确定TypeHandler类型以及它能够处理的数据库类型和Java类型

                Class<?> javaTypeClass = resolveClass(javaTypeName);

                JdbcType jdbcType = resolveJdbcType(jdbcTypeName);

                Class<?> typeHandlerClass = resolveClass(handlerTypeName);

                // 调用TypeHandlerRegistry.register()方法注册TypeHandler

                if (javaTypeClass != null) {

                    if (jdbcType == null) {

                        typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);

                    } else {

                        typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);

                    }

                } else {

                    typeHandlerRegistry.register(typeHandlerClass);

                }

            }

        }

    }

}

3.4.11 mapper解析

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) { // 循环遍历  标签的所有的子节点
        // 不同的定义方式的扫描,最终都是调用 addMapper()方法(添加到 MapperRegistry)。这个方法和 getMapper() 对应
        // package	包
        if ("package".equals(child.getName())) { // 
          String mapperPackage = child.getStringAttribute("name");
          // 扫描指定的包,并向MapperRegistry注册Mapper接口
          // 每一个类型 创建一个对应的 MapperProxyFactory 对象
          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) {
            // resource	相对路径
            ErrorContext.instance().resource(resource);
            // 读取映射文件
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // XMLMapperBuilder 解析映射文件 XMLConfigurationBuilder
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            // 解析 Mapper.xml,总体上做了两件事情 >>
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            // url	绝对路径
            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 	单个接口
            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.");
          }
        }
      }
    }
  }

3.5 映射文件解析

mapperParser.parse();
 public void parse() {
    // 总体上做了两件事情,对于语句的注册和接口的注册
    // 判断是否已经加载过了 映射文件
    if (!configuration.isResourceLoaded(resource)) {
      // 1、具体增删改查标签的解析。
      // 一个标签一个MappedStatement。 >>
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      // 2、把namespace(接口类型)和工厂类绑定起来,放到一个map。
      // 一个namespace 一个 MapperProxyFactory >>
      bindMapperForNamespace(); // 注册 Mapper 接口
    }
    // 处理 configurationElement 方法中解析失败的  节点
    parsePendingResultMaps();
    // 处理 configurationElement 方法中解析失败的  节点
    parsePendingCacheRefs();
    // 处理 configurationElement 方法中解析失败的 SQL 语句节点
    parsePendingStatements();
  }

3.5.1 configurationElement()——解析所有的子标签,最终获得MappedStatement对象。

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);
      // 添加缓存对象 如果我们希望多个 namespace 共用同一个二级缓存 就可以使用
      cacheRefElement(context.evalNode("cache-ref"));
      // 解析 cache 属性,添加缓存对象
      cacheElement(context.evalNode("cache"));
      // 创建 ParameterMapping 对象 以废弃  不推荐
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      // 创建 List
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      // 解析可以复用的SQL  includ
      sqlElement(context.evalNodes("/mapper/sql"));
      // 解析增删改查标签,得到 MappedStatement >> 一个 CRUD标签中的信息都被封装到了  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);
    }
  }

在buildStatementFromContext()方法中,创建了用来解析增删改查标签的XMLStatementBuilder,并且把创建的MappedStatement添加到mappedStatements中。

 private void buildStatementFromContext(List<XNode> list) {
    // 多数据库
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    // 解析 Statement >>
    buildStatementFromContext(list, null);
  }

  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      // 用来解析增删改查标签的 XMLStatementBuilder
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        // 解析 Statement,添加 MappedStatement 对象 >>
        // 解析具体的 insert/update/delete/select
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

关键步骤:

 MappedStatement statement = statementBuilder.build();
    // 最关键的一步,在 Configuration 添加了 MappedStatement >> 对应的就是一个 CRUD 标签
    configuration.addMappedStatement(statement);
    return statement;

3.5.2 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) {
        // 判断 在MapperRegistry中是否注册的有当前类型的 MapperProxyFactory对象
        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.addLoadedResource("namespace:" + namespace);
          // 添加到 MapperRegistry,本质是一个 map,里面也有 Configuration >>
          configuration.addMapper(boundType);
        }
      }
    }
  }

通过源码分析发现主要是是调用了addMapper()。addMapper()方法中,把接口类型注册到MapperRegistry中:
实际上是为接口创建一个对应的MapperProxyFactory(用于为这个type提供工厂类,创建MapperProxy)。

 public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) { // 检测 type 是否为接口
      if (hasMapper(type)) { // 检测是否已经加装过该接口
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        // !Map, MapperProxyFactory> 存放的是接口类型,和对应的工厂类的关系
        knownMappers.put(type, new MapperProxyFactory<>(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.

        // 注册了接口之后,根据接口,开始解析所有方法上的注解,例如 @Select >>
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

再进入parse()方法:

 public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      // 先判断 Mapper.xml 有没有解析,没有的话先解析 Mapper.xml(例如定义 package 方式)
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      // 处理 @CacheNamespace
      parseCache();
      // 处理 @CacheNamespaceRef
      parseCacheRef();
      // 获取所有方法
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
            // 解析方法上的注解,添加到 MappedStatement 集合中 >>
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }

4. 总结

总结:

  1. 我们主要完成了config配置文件、Mapper文件、Mapper接口中注解的解析。
  2. 我们得到了一个最重要的对象Configuration,这里面存放了全部的配置信息,它在属性里面还有各种各样的容器。
  3. 最后,返回了一个DefaultSqlSessionFactory,里面持有了Configuration的实例。

你可能感兴趣的:(#,MyBatis,mybatis,xml,java)