Mybatis源碼分析(三):讀取配置文件

在第一篇文章中使用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個加載器將不會使用。效果以下圖所示。
image.png圖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下,咱們分別來演示、驗證。
image.png圖2.net

要正確讀取這個文件應該是:線程

InputStream inputStream1 = ResourceTest.class.getClassLoader().getResourceAsStream("1.txt");

或者

InputStream inputStream2 = ResourceTest.class.getResourceAsStream("/1.txt");

若是對ClassLoader的getResourceAsStream打個斷點,會發現確實是從classpath下找。
image.png圖3

當咱們把Class的getResourceAsStream方法路徑中的'/'去掉,變成

InputStream inputStream2 = ResourceTest.class.getResourceAsStream("1.txt");

時,咱們斷點一下發現是這樣的
image.png圖4
image.png圖5
image.png圖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有關,咱們暫時先不研究它。

相關文章
相關標籤/搜索