在第一篇文章中使用Mybatis代码以下:sql
@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,即系统的入口点所使用的ClassLoadermybatis
根据ClassLoader传递性,当前类会由初始调用 main 方法的这个 ClassLoader全权负责,它就是AppClassLoader。也就是说在没有指定加载器的时候,1和3是同一个类加载器AppClassLoader。2中的线程加载器是为了打破双亲委托模型而设计的加载器,它能够作到跨线程共享类,只要它们共享同一个 contextClassLoader。父子线程之间会自动传递 contextClassLoader,因此共享起来将是自动化的。
若是不一样的线程使用不一样的 contextClassLoader,那么不一样的线程使用的类就能够隔离开来。打破双亲委托机制的文章能够参考一下:https://blog.csdn.net/xiaobao...
contextClassLoader在没有设置的状况下,默认使用的也是AppClassLoader。app
ClassLoaderWrapper中按照功能分为三类,classForName()方法,getResourceAsURL()方法和getResourceAsStream()方法,它们最终调用的都是ClassLoader类中的Class.forName(name, true, cl)方法、getResourceAsStream()方法以及getResource()方法。以getResourceAsStream()方法为例,源代码以下:maven
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()中,源代码以下:ui
ClassLoader[] getClassLoaders(ClassLoader classLoader) { return new ClassLoader[]{ classLoader, defaultClassLoader, Thread.currentThread().getContextClassLoader(), getClass().getClassLoader(), systemClassLoader}; }
结合类的声明和构造器代码:this
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个加载器,第四、5个加载器将不会使用。效果以下图所示。图1
前两个对象都是null,后三个对象都是AppClassLoader,使用第三个对象线程加载器,在class路径下查找,若是找不到尝试使用绝对路径查找。spa
PS:咱们讲一下
ClassLoader.getResourceAsStream()与Class.getResourceAsStream()加载文件路径区别:
ClassLoader是从jar包根目录查找,即classpath的路径下,读取classpath下的文件不须要加"/"
Class的getResourceAsStream方法是从当前.class 文件路径查找资源,读取classpath下的文件要加"/",以绝对路径方式加载。好比classpath下有一个1.txt的文件,代码在last.soul.resource的package下,咱们分别来演示、验证。图2.net
要正确读取这个文件应该是:线程
InputStream inputStream1 = ResourceTest.class.getClassLoader().getResourceAsStream("1.txt");
或者
InputStream inputStream2 = ResourceTest.class.getResourceAsStream("/1.txt");
若是对ClassLoader的getResourceAsStream打个断点,会发现确实是从classpath下找。图3
当咱们把Class的getResourceAsStream方法路径中的'/'去掉,变成
InputStream inputStream2 = ResourceTest.class.getResourceAsStream("1.txt");
时,咱们断点一下发现是这样的图4
图5
图6
发现class.getResourceAsStream是从last/soul/resource路径开始查找的(当前类的相对路径图5),会找不到文件,如图6所示。可是加上'/'以后他会从绝对路径,即项目根路径开始找,就找到了。
还有一个知识点,若是使用
InputStream f3 = Object.class.getClassLoader().getResourceAsStream("1.txt");
会报空指针异常,由于Object类是根加载器加载的,
若是某个 Class 对象的 classLoader 属性值是 null,那么就表示这个类也是「根加载器」加载的,反之亦然,若是某个 Class 对象 是「根加载器」加载的,那么Object.class.getClassLoader()==null。
因此咱们要用本身的Class,不要用Object等jdk提供的类。
另外咱们图2当中,在当前类的路径下有一个2.txt,不管何种方式也加载不到这个文件。后来发现是这个文件并无编译到target目录,这个应该和maven有关,咱们暂时先不研究它。