1. 程式人生 > >Mybatis建立SqlSession的原始碼分析

Mybatis建立SqlSession的原始碼分析

我們使用sqlSession之前,需要去獲取配置檔案,獲取InputStream輸入流,通過SqlSessionFactoryBuilder獲取sqlSessionFactory物件,從而獲取sqlSession。

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

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自定義過的類載入器。

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.我們可以看出呼叫了下面這個內部方法,裡面呼叫了封裝的方法,一個是獲取當前的類載入器,另一個是傳進來的檔名:

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

4.檢視getClassLoaders這個方法,可以看到裡面初始化了一個類載入器的陣列,裡面有很多個類載入器,包括預設的類載入器,當前執行緒的上下文類載入器,系統類載入器等。

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,那麼就會預設選擇第二個預設類載入器,而且我們可以知道如果檔名前面沒有加“/”,獲取到空物件的話,會自動加上“/”再訪問一遍:

 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)這個方法,我們可以看到裡面都是呼叫parent的getResource()方法,如果已經是父載入器,那麼就使用getBootstrapResource(name)獲取,如果獲取出來是空的,再根據getBootstrapResource(name)方法獲取。

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。

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

跟進去,我們可以看到在裡面shiyong了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);
  }

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物件就根據配置檔案創建出來了。