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