MyBatis 源码学习 | Day 2 | MyBatis 初始化

运行阶段划分

根据上一篇MyBatis 源码学习 | Day 1 | 了解 MyBatis中最后使用 MyBatis 操作数据库的代码,我们可以把程序整体运行的流程划分为两个部分:

  1. MyBatis 初始化
  2. 数据读写阶段
java 复制代码
/**
 * 使用 MyBatis 操作数据库
 *
 * @author nx-xn2002
 * @date 2024-08-02
 */
public class QueryWithMyBatis {
    public static void main(String[] args) throws IOException {
        //第一阶段:MyBatis初始化
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        //第二阶段:数据读写阶段
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = mapper.listAll();
        users.forEach(System.out::println);
        User user = mapper.selectUserById(1L);
        System.out.println(user);
        sqlSession.close();
    }
}

今天我们来探究第一阶段:MyBatis的初始化

MyBatis 初始化阶段

在 MyBatis 的初始化阶段,主要包含配置文件的解析和数据库连接等工作,可以看到,依次是调用了 Resource 类下的 getResourceAsStream 方法来把配置文件解析成 Stream 类对象,然后通过 SqlSessionFactoryBuilder 类下的 bulid 方法获取到 SqlSessionFactory 对象来管理数据库连接。我们深入到这两个方法中去

解析配置文件为输入流

Resource 类下的 getResourceAsStream 方法,试图获取到一个 InputStream 类的对象,核心方法的源代码如下:

java 复制代码
/**
 * Returns a resource on the classpath as a Stream object
 *
 * @param loader   The classloader used to fetch the resource
 * @param resource The resource to find
 * @return The resource
 * @throws java.io.IOException If the resource cannot be found or read
*/
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;
}

可以看到,这里是调用了 classLoaderWrapper 对象的同名方法来获取返回值,这个对象是 Resource 类的静态变量,它是在 Resource 类被加载时初始化的,类的加载过程包括:加载 -> 验证 -> 准备 -> 解析 -> 初始化,而 classLoaderWrapper 对象的赋值,就发生在初始化阶段

继续向下定位,可以看到 classLoaderWrapper 对象的这个方法,实际上是调用了另一个重载方法,方法的参数中 ClassLoader 对象变成了 ClassLoader 数组

java 复制代码
/**
  * Get a resource from the classpath, starting with a specific class loader
  *
  * @param resource    - the resource to find
  * @param classLoader - the first class loader to try
  * @return the stream or null
  */
 public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
   return getResourceAsStream(resource, getClassLoaders(classLoader));
 }
/**
 * Try to get a resource from a group of classloaders
 *
 * @param resource    - the resource to get
 * @param classLoader - the classloaders to examine
 * @return the resource or null
 */
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
  for (ClassLoader cl : classLoader) {
    if (null != cl) {
      // try to find the resource as passed
      InputStream returnValue = cl.getResourceAsStream(resource);
      // now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource
      if (null == returnValue) {
        returnValue = cl.getResourceAsStream("/" + resource);
      }
      if (null != returnValue) {
        return returnValue;
      }
    }
  }
  return null;
}

我们可以从 getClassLoaders 方法中,知道传入的 ClassLoader 数组的大致内容

java 复制代码
ClassLoader[] getClassLoaders(ClassLoader classLoader) {
  return new ClassLoader[]{
      classLoader,
      defaultClassLoader,
      Thread.currentThread().getContextClassLoader(),
      getClass().getClassLoader(),
      systemClassLoader};
}

结合上下文可以知道,数组中 classLoader 就是我们可以选择传入的自定义 ClassLoaderdefaultClassLoader 也是自定义的 ClassLoader 可以在 Resource 类中通过 set 方法传入,这两个默认值都是 nullThread.currentThread().getContextClassLoader() 这个方法返回当前线程的上下文类加载器,上下文类加载器可以被设置为任何类加载器,但默认情况下,它通常是指向应用程序类加载 AppClassLoadergetClass().getClassLoader() 这个方法返回加载当前类的类加载器,对于大多数非基本类来说,这将是应用程序类加载器 AppClassLoader,而对于 Java 核心库中的类,如 java.lang.Object,则会被引导类加载器 Bootstrap ClassLoader 加载。systemClassLoader 这个变量在当前类初始化是就被赋值了,通常是指向系统类加载器 AppClassLoader 的引用。

java 复制代码
//systemClassLoader 的初始化
ClassLoaderWrapper() {
  try {
    systemClassLoader = ClassLoader.getSystemClassLoader();
  } catch (SecurityException ignored) {
    // AccessControlException on Google App Engine
  }
}

以上就是解析配置文件的基本流程,最后通过尝试调用各个类加载器的解析配置文件的方法,来对配置文件进行解析,在这个过程中,大量使用了方法重载,使得程序变得灵活

获取 SqlSessionFactory 对象管理数据库连接

在这一步骤中,首先我们可以看到 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 一句中使用 SqlSessionFactoryBuilder 类的 build 方法来构建 SqlSessionFactory 类对象,进入到这个类中可以注意到,它体现了建造者模式的思想,我们看到这个类的核心方法:

java 复制代码
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.
		}
	}
}

在这里面,核心的两句代码是

java 复制代码
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());

这里面的 parser.parse() 提供了一个 Configuration 类的对象供 SqlSessionFactory 类使用 SqlSessionFactory build(Configuration config) 方法来构造 SqlSessionFactory 对象

可以看 parse 方法的实现细节:

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

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);
  }
}

可以看到在这里就是配置文件的解析过程,从配置文件的根节点开始,逐层进行解析,也包括其中相关的映射文件,解析过程里,不断把解析结果放入到 Configuration 对象中,最后通过这个 Configuration 对象,构造 SqlSessionFactory 对象。

相关推荐
汤姆和佩琦7 分钟前
2024-12-25-sklearn学习(20)无监督学习-双聚类 料峭春风吹酒醒,微冷,山头斜照却相迎。
学习·聚类·sklearn
好学近乎知o19 分钟前
正则表达式(学习Django过程中可能涉及的)
学习·正则表达式·django
雨中奔跑的小孩22 分钟前
爬虫学习案例8
爬虫·学习
jieshenai24 分钟前
使用 VSCode 学习与实践 LaTeX:从插件安装到排版技巧
ide·vscode·学习
灰太狼不爱写代码3 小时前
CUDA11.4版本的Pytorch下载
人工智能·pytorch·笔记·python·学习
秋恬意8 小时前
Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别
java·数据库·mybatis
eybk9 小时前
Pytorch+Mumu模拟器+萤石摄像头实现对小孩学习的监控
学习
6.949 小时前
Scala学习记录 递归调用 练习
开发语言·学习·scala
守护者17010 小时前
JAVA学习-练习试用Java实现“使用Arrays.toString方法将数组转换为字符串并打印出来”
java·学习
学会沉淀。10 小时前
Docker学习
java·开发语言·学习