Mybatis【2.1】-- 從讀取流到建立SqlSession發生了什麼?

[TOC]
pexels-pixabay-459225java

咱們使用sqlSession以前,須要去獲取配置文件,獲取InputStream輸入流,經過SqlSessionFactoryBuilder獲取sqlSessionFactory對象,從而獲取sqlSessionsql

InputStream is = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession();

1.Resources.getResourceAsStream("mybatis.xml")到底作了什麼?

1.首先咱們來看InputStream is = Resources.getResourceAsStream("mybatis.xml");這句話到底替咱們幹了什麼,下面能夠看出在裏面調用了另外一個內部方法,resource是全局配置的文件名:數組

public static InputStream getResourceAsStream(String resource) throws IOException {
    // 從這裏字面意思是傳一個空的類加載器進去,還有全局配置文件名,從方法名的意思就是
    // 將配置文件讀取,轉化成輸入流
    return getResourceAsStream((ClassLoader)null, resource);
  }

2.跟進方法中,咱們能夠知道在裏面調用ClassLoaderWrapper類的一個實例對象的getResourceAsStream()方法,這個classLoaderWrapper怎麼來的呢?這個是Resources.class的一個成員屬性,那麼這個ClassLoaderWrapper是什麼東西呢?緩存

Resources.class中咱們只是使用private static ClassLoaderWrapper classLoaderWrapper = new ClassLoaderWrapper();建立一個classLoaderWrapper對象。
ClassLoaderWrapper實際上是一個ClassLoader(類加載器)的包裝類,其中包含了幾個ClassLoader對象,一個defaultClassLoader,一個systemClassLoader,經過內部控制,能夠確保返回正確的類加載器給系統使用。咱們能夠當成一個mybatis自定義過的類加載器。session

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);
    } else {
      return in;
    }
  }

3.咱們能夠看出調用了下面這個內部方法,裏面調用了封裝的方法,一個是獲取當前的類加載器,另外一個是傳進來的文件名:mybatis

public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
    return this.getResourceAsStream(resource, this.getClassLoaders(classLoader));
  }

4.查看getClassLoaders()這個方法,能夠看到裏面初始化了一個類加載器的數組,裏面有不少個類加載器,包括默認的類加載器,當前線程的上下文類加載器,系統類加載器等。app

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

5.進入getResourceAsStream(String resource, ClassLoader[] classLoader)這個方法內部,咱們能夠看到裏面遍歷全部的類加載器,使用類加載器來加載獲取InputStream對象,咱們能夠知道里面是選擇第一個適合的類加載器,若是咱們不傳類加載器進去,那麼第一個本身定義的類加載器就是null,那麼就會默認選擇第二個默認類加載器,並且咱們能夠知道若是文件名前面沒有加「/」,獲取到空對象的話,會自動加上「/」再訪問一遍:ide

InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
    ClassLoader[] arr$ = classLoader;
    int len$ = classLoader.length;
    for(int i$ = 0; i$ < len$; ++i$) {
      ClassLoader cl = arr$[i$];
      if (null != cl) {
        InputStream returnValue = cl.getResourceAsStream(resource);
        if (null == returnValue) {
          returnValue = cl.getResourceAsStream("/" + resource);
        }
        if (null != returnValue) {
          return returnValue;
        }
      }
    }
    return null;
  }

6.咱們進入類加載器加載資源文件的代碼中,咱們能夠看到首先獲取全路徑的url,而後再調用openStream()學習

public InputStream getResourceAsStream(String name) {
    URL url = getResource(name);
    try {
        return url != null ? url.openStream() : null;
    } catch (IOException e) {
        return null;
    }
}

6.1.咱們跟進getResource(name)這個方法,咱們能夠看到裏面都是調用parentgetResource()方法,若是已是父加載器,那麼就使用getBootstrapResource(name)獲取,若是獲取出來是空的,再根據getBootstrapResource(name)方法獲取。ui

public URL getResource(String name) {
        URL url;
        if (parent != null) {
            url = parent.getResource(name);
        } else {
            url = getBootstrapResource(name);
        }
        if (url == null) {
            url = findResource(name);
        }
        return url;
    }

6.1.1咱們跟進去getBootstrapResource(name);

private static URL getBootstrapResource(String name) {
        URLClassPath ucp = getBootstrapClassPath();
        Resource res = ucp.getResource(name);
        return res != null ? res.getURL() : null;
    }

6.1.1.1咱們看到getBootstrapClassPath()這個方法,這個方法的裏面調用了引入的包,讀取的是類加載器的加載路徑,這個方法到此爲止,再深刻就回不去了:)。

static URLClassPath getBootstrapClassPath() {
        return sun.misc.Launcher.getBootstrapClassPath();
    }

6.1.1.2 咱們看ucp.getResource(name)這個方法,咱們能夠看到在裏面調用了這個方法,這個方法主要是查找緩存,而後遍歷找到第一個符合條件的加載器來獲取resource,到此咱們再也不深究下去,得往上一層回頭看:

public Resource getResource(String var1, boolean var2) {
    if (DEBUG) {
      System.err.println("URLClassPath.getResource(\"" + var1 + "\")");
    }

    int[] var4 = this.getLookupCache(var1);

    URLClassPath.Loader var3;
    for(int var5 = 0; (var3 = this.getNextLoader(var4, var5)) != null; ++var5) {
      Resource var6 = var3.getResource(var1, var2);
      if (var6 != null) {
        return var6;
      }
    }

    return null;
  }

咱們知道getBootstrapResource(name)裏面主要是url(文件資源的路徑),而後使用url.openStream()去獲取stream流:

public final InputStream openStream() throws java.io.IOException {
        return openConnection().getInputStream();
    }

咱們來看openConnection()方法,裏面調用的是一個抽象方法,獲取的是一個URLConnection(url鏈接對象):

public URLConnection openConnection() throws java.io.IOException {
        return handler.openConnection(this);
    }

再看getInputStream()這個方法,咱們能夠看到這是一個接口方法,咱們找到FileURLConnection的這個方法,這是一個單線程處理文件URL的inputstream的方法:

public synchronized InputStream getInputStream() throws IOException {
    this.connect();
    if (this.is == null) {
      if (!this.isDirectory) {
        throw new FileNotFoundException(this.filename);
      }
      FileNameMap var3 = java.net.URLConnection.getFileNameMap();
      StringBuffer var4 = new StringBuffer();
      if (this.files == null) {
        throw new FileNotFoundException(this.filename);
      }
      Collections.sort(this.files, Collator.getInstance());
      for(int var5 = 0; var5 < this.files.size(); ++var5) {
        String var6 = (String)this.files.get(var5);
        var4.append(var6);
        var4.append("\n");
      }
      this.is = new ByteArrayInputStream(var4.toString().getBytes());
    }
    return this.is;
  }

到這裏,整個獲取inputstream的過程已經結束,只要把返回值往上一層返回就能夠獲得這個配置文件所須要的inputstream。

2. new SqlSessionFactoryBuilder().build(is)的運行原理

首先SqlSessionFactoryBuilder的無參數構造方法是沒有任何操做的:

public SqlSessionFactoryBuilder() {
  }

那麼咱們看build(is)這個方法,裏面調用了一個封裝方法,一個是inputstream,一個是string,一個是屬性對象:

public SqlSessionFactory build(InputStream inputStream) {
    return this.build((InputStream)inputStream, (String)null, (Properties)null);
  }

跟進去,咱們能夠看到在裏面使用了xmlconfigbuilder,也就是xml配置構造器,實例化一個xml配置對象,可想而知,也就是咱們的mybatis.xml所對應的配置對象構造器,在裏面調用了另外一個build()方法:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    SqlSessionFactory var5;
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      var5 = this.build(parser.parse());
    } catch (Exception var14) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
    } finally {
      ErrorContext.instance().reset();

      try {
        inputStream.close();
      } catch (IOException var13) {
        ;
      }

    }

    return var5;
  }

咱們能夠看到調用的另外一個build方法,也就是使用配置對象構建一個DefaultSqlSessionFactory對象,在上面返回這個對象,也就是咱們的sqlsessionFactory。

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

3. sqlSessionFactory.openSession()獲取sqlSession

咱們能夠看到其實這個是sqlSessionFactory的一個接口,其實現類是DefaultSqlSessionFactory,那麼方法以下:

public SqlSession openSession() {
    return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
  }

咱們查看openSessionFromDataSource()這個方法,從名字能夠大概知道是從數據源加載Sqlsession,裏面能夠指定執行器類型,事物隔離級別,還有是否自動提交,若是不設定,那麼默認是null以及false,在方法內主要作的是將配置文件對象的環境取出來構造事務工廠,配置執行器等,返回一個DefaultSqlSession的實例。

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    DefaultSqlSession var8;
    try {
      Environment environment = this.configuration.getEnvironment();
      TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      Executor executor = this.configuration.newExecutor(tx, execType);
      var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
    } catch (Exception var12) {
      this.closeTransaction(tx);
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
    } finally {
      ErrorContext.instance().reset();
    }
    return var8;
  }

到此爲止,一個sqlsession對象就根據配置文件建立出來了。

此文章僅表明本身(本菜鳥)學習積累記錄,或者學習筆記,若有侵權,請聯繫做者刪除。人無完人,文章也同樣,文筆稚嫩,在下不才,勿噴,若是有錯誤之處,還望指出,感激涕零~

技術之路不在一時,山高水長,縱使緩慢,馳而不息。
公衆號:秦懷雜貨店

相關文章
相關標籤/搜索