Mybatis源码阅读(一)

198 阅读3分钟

前言

本系列主要讲解Mybatis源码的阅读过程,如果没有用过的mybatis的同学可以先去找一些教程简单的使用之后再回来看。本次阅读从几条主线进入,分析Mybatis的实现原理及其中的一些设计模式的应用。同时,下面给出来的代码大部分我都会加上注释,方便大家自己阅读,希望可以为想要阅读源码的同学提供一些思路及帮助。
第一章内容主要讲解主配置文件的加载过程。 

配置文件加载

- 配置文件加载的入口-SqlSessionFactoryBuilder
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      // XMLConfigBuilder:用来解析XML配置文件
      // 使用构建者模式
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      // parser.parse():使用XPATH解析XML配置文件,将配置文件封装为Configuration对象
      // 返回DefaultSqlSessionFactory对象,该对象拥有Configuration对象(封装配置文件信息)
      return build(parser.parXQse());
    } 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.
      }
    }
  }

该类中使用了很多的重载方法去满足不同的加载需求场景。

  • 接着我们看下解析配置文件的具体类-XMLConfigBuilder
  • 下面是具体的解析过程
public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //通过XPATH解析器,解析configuration根节点
    //从configuration根节点开始解析,最终将解析出的内容封装到Configuration对象中
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
  
//具体解析过程,主要是对各个标签的解析
private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      // 解析</properties>标签
      propertiesElement(root.evalNode("properties"));
      // 解析</settings>标签
      Properties settings = settingsAsProperties(root.evalNode("settings"));
     ......
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

其中对mappers标签的解析有几种不同的方式,在这里跟大家说明下,其他具体标签的解析大家可以点进去看,这里就不细讲了。 下面我们一起看下对mappers标签的解析。

if (parent != null) {
      // 获取<mappers>标签的子标签
      for (XNode child : parent.getChildren()) {
    	// <package>子标签
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          // 会将配置的包下所有的mapper接口信息存储到一个Map集合中,key为mapper接口类型,value为代理对象工厂
          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);
            // 用来解析mapper映射文件
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            // 通过XMLMapperBuilder解析mapper映射文件
            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());
            // 通过XMLMapperBuilder解析mapper映射文件
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            // 将指定mapper接口以存储到一个Map集合中,key为mapper接口类型,value为代理对象工厂
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }

这个就是mappers文件的加载过程,其中如果配置的是package标签或者获取到的是class的值,那么会将mapper接口信息存储到一个Map集合中,key为mapper接口类型,value为代理对象工厂,可以通过代理对象工厂获取对应的代理对象。而对应的mapper文件的解析是在添加之后通过注解的方式解析对应的mapper文件信息。

// 用来解析注解方式的mapper接口
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);

这里我们就不继续深入了。对mapper文件具体的解析过程我们后面会看到。而其他的通过resource和url指定对应的mapper文件路径的就通过XMLMapperBuilder去解析相应的信息,然后封装到sqlSource中。下一篇文章会讲解对mapper.xml文件的具体解析过程。