Skip to content

上文中编写的测试代码如下,我们来逐行进行探究。

java
@Test
public void test1() throws IOException {
    // 1. 通过类加载器对配置文件进行加载,加载成了字节输入流,存到内存中 注意:配置文件并没有被解析
    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");

    // 2. (1)解析了配置文件,封装configuration对象 (2)创建了DefaultSqlSessionFactory工厂对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

    // 3.问题:openSession()执行逻辑是什么?
    // 3.(1)创建事务对象 (2)创建了执行器对象cachingExecutor (3)创建了DefaultSqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 4. 委派给Executor来执行,Executor执行时又会调用很多其他组件(参数设置、解析sql的获取,sql的执行、结果集的封装)
    User user = sqlSession.selectOne("com.itheima.mapper.UserMapper.findByCondition", 1);

    System.out.println(user);
    System.out.println("MyBatis源码环境搭建成功....");
    sqlSession.close();
}

通过类加载器对配置文件进行加载

第一行是通过类加载对配置文件进行加载,返回输入流,此时配置文件并没有被解析;classLoaderWrapper本质上就是调用一个合适的类加载器,获取到resources目录的资源转换为输入流对象返回。

java
/**  
   * 读取resources目录下的文件,转换为输入流返回
   * @param resource      - 文件路径
   * @param classLoader  - 类加载器
   */
public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
    // classLoaderWrapper: ClassLoader 的包装器
    InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
    if (in == null) {
        throw new IOException("Could not find resource " + resource);
    }
    return in;
}

我们来看classLoaderWrapper.getResourceAsStream()这个方法,通过类加载器,从类路径获取资源;注意此时我们classLoader传递的是null,但是mybatis还有其他的默认类加载器,注意看getClassLoaders方法;

java
// 通过类加载器,从类路径获取资源
public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
    // getClassLoaders
    return getResourceAsStream(resource, getClassLoaders(classLoader));
}
// 
ClassLoader[] getClassLoaders(ClassLoader classLoader) {
    return new ClassLoader[] {
        classLoader, // 参数指定的类加载器
        defaultClassLoader,  // 系统指定的默认加载器
        Thread.currentThread().getContextClassLoader(), // 当前线程绑定的类加载器
        getClass().getClassLoader(), // 当前类使用的类加载器
        systemClassLoader
    };
}

再来看getResourceAsStream()方法,这是获取资源转换为输入流的主要逻辑:

  • 通过给定的资源名和类加载器数组,循环遍历类加载器,从资源的路径中获取输入流。
  • 首先尝试使用指定的类加载器读取资源,如果未找到,则再尝试使用默认的类加载器(上文代码显示了默认的类加载器)。
  • 如果找到了资源,返回资源的输入流;如果未找到资源,返回null。
java
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
    // 循环ClassLoader,通过指定或默认的ClassLoader读取文件
    for (ClassLoader cl : classLoader) {
        if (null != cl) {
            InputStream returnValue = cl.getResourceAsStream(resource);
            if (null == returnValue) {
                // 如果没有获取到资源,在路径前+“/”再尝试获取一遍
                returnValue = cl.getResourceAsStream("/" + resource);
            }
            if (null != returnValue) {
                return returnValue;
            }
        }
    }
    return null;
}

小结

第一行代码就是使用类加载器获取配置文件的内容,转换为输入流的形式;类加载如果传递为null,会有默认的类加载器,当前线程的类加载器、当前类的类加载器。