MyBatis 初始化过程,把握 MyBatis 整体启动流程

235 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第 18 天,点击查看活动详情

我们在使用 MyBatis 与数据库进行交互时,有两种方式,一种传统方式,一种 mapper 代理方式。接下来我们会分别针对这两种方式都进行下源码剖析。

我们通过对传统方式的源码剖析要去知道 MyBatis 的底层它是如何去加载、解析配置文件的以及是如何去执行 SQL 语句、设置参数以及封装返回结果集。

然后,我们会再对 mapper 代理方式进行源码剖析。我们通过这一部分源码剖析,要去知道 MyBatis 底层是怎么去产生代理对象,当代理对象调用方法时,它又是怎么去执行到底层的 JDBC 代码。

接下来我们会分别针对这两种方式都进行下源码剖析。

在源码剖析之前,大家可以回忆一下,我们在第一节完成的自定义持久层框架。当时在编写自定义持有层框架的时候,已经介绍它就是 MyBatis 的一个雏形。如果现在大家已经能够知道这个雏形是怎么形成,其实对于我们现在翻阅源码是很有帮助的。

不管现在是传统方式,还是 mapper 代理方式,加载配置文件和初始化过程都是一样的。

读取配置文件

在 MyBatis 初始化过程中,MyBatis 会读取 mybatis-config.xml 核心配置文以及所有的mapper.xml 映射配置文件,同时还加载这两个配置文件的指定类以及解析类中相关注解,最终解析得到的内容转成配置对象,加载完成之后,MyBatis 会根据配置对象初始化各个模块。

首先需要加载核心配置文件,就需要传入核心配置文件的路径。读取配置文件,读成字节输入流,但是要注意要注意,现在还没解析,只是把它加载成了字节输入流。

//读取配置文件,读成字节输入流
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");

Resources.getResourceAsStream() 方法进行源码剖析,在这个方法中,所要完成的功能就是去调用了 getResourceAsStream() 重载方法。在调用这个重载方法时,需要传递两个参数,分别是 null 值和核心配置文件的路径。

public static InputStream getResourceAsStream(String resource) throws IOException {
  return getResourceAsStream(null, resource);
}

在这个 getResourceAsStream() 方法中,执行的方法就是 classLoaderWrapper.getResourceAsStream() 方法,其实还是去借助类加载器 getResourceAsStream() 方法来根据当前配置文件路径,把它加载成字节输入流进行返回。

public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
  InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
  if (in == null) {
    throw new IOException("Could not find resource " + resource);
  }
  return in;
}

初始化

这一步才是初始化的关键。

通过 new SqlSessionFactoryBuilder() 方法去调用 build() 的方法,其实 build() 的方法才是初始化工作的一个真正开始。读取配置文件只是一个工具类,把配置文件加载成字节输入流,把加载出来的字节输入流传入到 build() 方法中,并且拿到返回结果是一个 sqlSessionFactory。

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

在剖析 MyBatis 之前,先来回顾之前我们在自定义持久层框架,介绍过 build() 方法要完成两个功能。第一个功能,它要去解析我们的解析核心配置文件,把它解析出来的内容封装成一个consideration 对象。第二个功能,创建 SqlSessionFactory 实现类对象。

在 MyBatis 框架中,build() 方法是不是这么实现的。首先,在 build() 方法调用了重载方法 build,传递了三个参数,分别是 inputStream、environment 和 properties,其中inputStream 是传入加载的字节流, environment 和 properties 的值都为 null。

public SqlSessionFactory build(InputStream inputStream) {
  return build(inputStream, null, null);
}

在这个 build 重载方法中,同样完成两个功能,第一个功能就是解析配置文件,封装成 consideration。第二功能就是创建 SqlSessionFactory 的实现类对象。

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    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.
    }
  }
}

先来看一下解析配置文件的步骤。

第一步,创建 XMLConfigBuilder 对象,这个对象的作用就是专门用来解析 MyBatis 配置文件类,通过 XMLConfigBuilder 类来解析 MyBatis 配置文件。parse() 方法就是真正的去执行 XML 解析了。

public Configuration parse() {
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}

在这个 parse() 方法中,首先使用 if 进行判断,判断当前配置文件有没有解析过,如果解析过,parsed 的值为 true,就会抛出 BuilderException 异常。如果没有解析过,parsed 值默认是 false,跳过 if 判断,继续往下执行。

第一次加载肯定是没有解析过的,所以继续往下执行,对配置文件进行解析。首先解析之前,先将 parsed 标记设置为 true。这样表示当前配置文件已经解析过的。

具体解析配置文件中的内容需要调用 parseConfiguration() 方法。在调用 parseConfiguration() 方法时,传递一个 parser.evalNode("/configuration") 参数,其中,这个 parser 就是 XPathParser 解析器对象,evalNode 方法就是用来读取节点的数据,读取就是 configuration 这个顶层标签,读取到这个 configuration 顶层标签之后,把这个标签当做参数传入到 parseConfiguration() 方法中,执行 parseConfiguration() 方法。

parseConfiguration() 方法定义了解析 mybatis-config.xml 配置文件的完整流程。

private void parseConfiguration(XNode root) {
  try {
    // issue #117 read properties first
    propertiesElement(root.evalNode("properties"));
    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"));
    settingsElement(settings);
    // read it after objectFactory and objectWrapperFactory issue #631
    environmentsElement(root.evalNode("environments"));
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    typeHandlerElement(root.evalNode("typeHandlers"));
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

通过 parseConfiguration() 方法源码 ,我们可以清晰地看到 XMLConfigBuilder 对 mybatis-config.xml 配置文件中各类标签的解析方法。解析核心步骤如下:

  • 解析 标签;

  • 解析 标签;

  • 解析<typeAliases>标签;

  • 解析<plugins>标签;

  • 解析 标签;

  • 解析 标签;

  • 解析<reflectorFactory>标签;

  • 解析 <environments> 标签;

  • 解析 <databaseIdProvider> 标签;

  • 解析 <typeHandlers> 标签;

  • 解析 <mappers> 标签。