转载

Mybatis源码分析(三):读取配置文件

在第一篇文章中使用Mybatis代码如下:

@Test
    public void test() throws IOException {
        String resource = "file/mybatis-config.xml";
        // 读取配置文件
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 构建sqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //获取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //构建参数
        Map map=new HashMap<>();
        map.put("id",1);
        User user=sqlSession.selectOne("last.soul.mapper.UserMapper.selectById",map);
        Assert.assertTrue(user.getId()==1);
    }

我们来分析一下Resources.getResourceAsStream()这个方法,查看代码内部。我们发现这个方法主要是调用ClassLoaderWrapper.getResourceAsStream()来实现的,ClassLoaderWrapper位于Mybatis源码的io包中,是一个classLoader的包装器。其中包含多个classLoader对象,通过调整加载器的顺序确保返回正确的类加载器,从而加载正确的文件。

在介绍ClassLoaderWrapper之前先详细讲一下类加载器。类加载器的文章可以参考: http://blog.itpub.net/3156126...

ClassLoader通常有以下几种:

A.this.getClass().getClassLoader(); // 当前类的ClassLoader

B.Thread.currentThread().getContextClassLoader(); //当前线程的ClassLoader

C.ClassLoader.getSystemClassLoader(); // 使用系统ClassLoader,即系统的入口点所使用的ClassLoader

根据ClassLoader传递性,当前类会由初始调用 main 方法的这个 ClassLoader全权负责,它就是AppClassLoader。也就是说在没有指定加载器的时候,1和3是同一个类加载器AppClassLoader。2中的线程加载器是为了打破双亲委托模型而设计的加载器,它可以做到跨线程共享类,只要它们共享同一个 contextClassLoader。父子线程之间会自动传递 contextClassLoader,所以共享起来将是自动化的。

如果不同的线程使用不同的 contextClassLoader,那么不同的线程使用的类就可以隔离开来。打破双亲委托机制的文章可以参考一下: https://blog.csdn.net/xiaobao...

contextClassLoader在没有设置的情况下,默认使用的也是AppClassLoader。

ClassLoaderWrapper中按照功能分为三类,classForName()方法,getResourceAsURL()方法和getResourceAsStream()方法,它们最终调用的都是ClassLoader类中的Class.forName(name, true, cl)方法、getResourceAsStream()方法以及getResource()方法。以getResourceAsStream()方法为例,源代码如下:

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

它的主要逻辑就是遍历ClassLoader对象数组,尝试读取文件,只要读取成功就返回,数组里的顺序就是优先级。其它两个方法逻辑类似,那么ClassLoader对象数组的顺序是如何定义的呢,这个逻辑在该类的另一个重要方法getClassLoaders()中,源代码如下:

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

结合类的声明和构造器代码:

ClassLoader defaultClassLoader;
  ClassLoader systemClassLoader;

  ClassLoaderWrapper() {
    try {
      systemClassLoader = ClassLoader.getSystemClassLoader();
    } catch (SecurityException ignored) {
      // AccessControlException on Google App Engine
    }
  }

按数组顺序,

1.classLoader是参数指定的类加载器,

2.defaultClassLoader就默认的类加载器,mybatis并没有给初始值,

3.Thread.currentThread().getContextClassLoader()是上文中的B,线程加载器,

4.getClass().getClassLoader()是上文中的A,类加载器,

5.systemClassLoader是上文中的C,系统加载器即系统的入口点所使用的ClassLoader,是AppClassLoader。

以文章开头的代码来看,没有使用参数指定类加载器所以1的加载器为null,mybatis没有给初始defaultClassLoader也为null,所以getResourceAsStream()方法使用的是第三个类加载器,即线程加载器,由于使用了第3个加载器,第4、5个加载器将不会使用。效果如下图所示。

Mybatis源码分析(三):读取配置文件

前两个对象都是null,后三个对象都是AppClassLoader,使用第三个对象线程加载器。

//下面再介绍一下,这几个加载器路径区别

原文  https://segmentfault.com/a/1190000023245428
正文到此结束
Loading...