下面我們正式進入mybatis的源碼學習,之前我們已經了解過mybatis中通過配置文件來保證與數據庫的交互。配置文件分為核心配置文件和映射配置文件,核心配置文件的主要作用就是加載數據庫的一些配置信息而映射配置文件則是執行對應的sql語句。同時核心配置文件中也會集成映射配置文件的路徑信息。
核心配置文件的加載
那么如果mybatis想要執行第一步就需要加載這些配置文件,保證后續與數據庫的操作能夠順利執行。下面我們看看加載的源碼。
//解析配置文件通過類加載器加載為stream流
InputStream resourceAsStream = Resources.getResourceAsStream("/configMapper.xml");//進行加載文件根據文件信息解析出SqlSessionFactory
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
最外層的方法加載配置文件只用到了兩個方法,分別是通過Resources類的方法得到InputStream?和SqlSessionFactoryBuilder的build來根據InputStream加載出SqlSessionFactoryBuilder。
下面我們進入getResourceAsStream方法來看看這個方法的作用
/*** Returns a resource on the classpath as a Stream object** @param resource* The resource to find** @return The resource** @throws java.io.IOException* If the resource cannot be found or read*/public static InputStream getResourceAsStream(String resource) throws IOException {return getResourceAsStream(null, resource);}/*** Returns a resource on the classpath as a Stream object** @param loader* The classloader used to fetch the resource* @param resource* The resource to find** @return The resource** @throws java.io.IOException* If the resource cannot be found or read*/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);}return in;}
在這個方法的內部又調用了重載方法而第一個參數loader則是類加載器,也就是說可以指定類加載器來進行加載這個文件,而在這個getResourceAsStream方法的內部則是調用了一個classLoaderWrapper來進行真正的加載。
/*** Get a resource from the classpath, starting with a specific class loader** @param resource* - the resource to find* @param classLoader* - the first class loader to try** @return the stream or null*/public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {return getResourceAsStream(resource, getClassLoaders(classLoader));}/*** Try to get a resource from a group of classloaders** @param resource* - the resource to get* @param classLoader* - the classloaders to examine** @return the resource or null*/InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {for (ClassLoader cl : classLoader) {if (null != cl) {// try to find the resource as passedInputStream 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 resourceif (null == returnValue) {returnValue = cl.getResourceAsStream("/" + resource);}if (null != returnValue) {return returnValue;}}}return null;}
點開classLoaderWrapper的這個方法可以發現依舊是調用了一個重載方法,并且在調用重載方法之前則是調用了getClassLoaders方法,那么我們打開方法進行查看
ClassLoader[] getClassLoaders(ClassLoader classLoader) {return new ClassLoader[] { classLoader, defaultClassLoader, Thread.currentThread().getContextClassLoader(),getClass().getClassLoader(), systemClassLoader };}
可以看到整個方法實際上就是生成了一個類加載器數組,而參數的作用就是在這個數組中再多添加一個類加載器,并且這個數組中的類加載器的順序也是有講究的,優先級高的類加載器則是放在前面,目的則是為了后續策略模式的調用。
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {for (ClassLoader cl : classLoader) {if (null != cl) {// try to find the resource as passedInputStream 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 resourceif (null == returnValue) {returnValue = cl.getResourceAsStream("/" + resource);}if (null != returnValue) {return returnValue;}}}return null;}
我們繼續回去看接下來的方法可以看到當進入重載方法的時候則是遍歷了作為參數傳遞進來的classLoader數組,然后通過遍歷每個類加載器來選擇正確的類加載器進行加載。這種方式實際上就是設計模式的策略模式,根據不同的參數動態的選擇不同的策略進行執行。
那么這個classLoaderWrapper的作用是什么呢
public class ClassLoaderWrapper {ClassLoader defaultClassLoader;ClassLoader systemClassLoader;ClassLoaderWrapper() {try {systemClassLoader = ClassLoader.getSystemClassLoader();} catch (SecurityException ignored) {// AccessControlException on Google App Engine}}}
點開這個類我們發現這個類內部含有多個類加載器,并且結合其他方法我們發現其實這個類是將多個類加載器進行集成,同時采用了策略模式的方式將不同的類加載器進行加載不同的文件將其轉化為InputStream。也就是說整個方法的核心就是在于這個ClassLoaderWrapper類,它內部集成了多個類加載器,并且根據傳遞的文件路徑通過此類加載器進行加載,最終返回成一個InputStream來表示整個方法的執行完畢。
至此getResourceAsStream方法解析完畢,那么我們從這個方法的源碼中其實可以學習到策略模式的使用以及在同一個類的不同重載方法并不是說是毫不相干的就像mybatis源碼中那樣,基本每個重載類最后都會調用另一個重載類,而最后真正執行業務邏輯的則只有一個重載類。其他類則是更多對于參數的封裝和校驗等操作最后調用真正執行業務邏輯的類。
之后我們再看看下一個方法
//進行加載文件根據文件信息解析出SqlSessionFactory
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
我們看到這個方法是通過一個SqlSessionFactory的構造器來根據InputStream進行構造返回一個SqlSessionFactory對象,這里有涉及到了兩種設計模式生成器模式和工廠模式
下面我們進入方法內部看看如何解析配置文件的
public class SqlSessionFactoryBuilder {public SqlSessionFactory build(Reader reader, String environment, Properties properties) {try {XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);return build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {if (reader != null) {reader.close();}} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}}public SqlSessionFactory build(InputStream inputStream) {return build(inputStream, null, null);}public SqlSessionFactory build(InputStream inputStream, String environment) {return build(inputStream, environment, null);}public SqlSessionFactory build(InputStream inputStream, Properties properties) {return build(inputStream, null, properties);}
}
上述代碼可以看出依舊是一個方法內部調用了重載方法,而真正執行業務邏輯的只有一個重載方法
可以看出最終是調用了這個方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);return build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {if (inputStream != null) {inputStream.close();}} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}}
而除了inputStream之外則是含有另外兩個參數,這兩個參數的主要作用就是選擇執行配置文件中的哪些配置,因為一個核心配置文件是可以有多個配置的數據源和運行環境的,而另外兩個參數則是決定優先使用哪個數據源和運行環境。
代碼第一行就new了一個XMLConfigBuilder對象,而這個XMLConfigBuilder對象用的很明顯也是生成器模式。下面我們進入這個對象內部來進行看看。
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {this(Configuration.class, inputStream, environment, props);}public XMLConfigBuilder(Class<? extends Configuration> configClass, InputStream inputStream, String environment,Properties props) {this(configClass, new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);}private XMLConfigBuilder(Class<? extends Configuration> configClass, XPathParser parser, String environment,Properties props) {super(newConfig(configClass));ErrorContext.instance().resource("SQL Mapper Configuration");this.configuration.setVariables(props);this.parsed = false;this.environment = environment;this.parser = parser;}
進入方法中依舊是調用了多個構造函數來進行參數封裝真正執行的業務邏輯的構造函數只有一個,其中我們看到Configuration這個類,這個類是整個mybatis加載文件的核心類,它的主要作用就是集成配置文件中所解析出來的所有信息于這個類中。并且后續的sql方法執行也是根據這個類內部的屬性進行數據源等其他信息的配置來完成執行。因此這個Configuration類則是整個mybatis加載配置文件中的核心中的核心。而后續的XPathParser類則是將inputStream流直接解析為document文件。XMLConfigBuilder內部會生成一個初始化的Configuration類,并且在后續的parse方法中進行Configuration類對象的賦值。可能會產生一個疑問,直接操作成員變量不會造成線程安全的問他碼,其實這里則是采用了一個非常巧妙的設計。線程隔離機制,我們發現所有直接操作成員變量的對象都是在方法體中直接new出來的對象,也就是說每個線程都有自己的獨有的對象,這樣進行訪問的時候就是單線程訪問了從而達成了線程隔離的效果。
public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);}
最終我們發現是通過XMLConfigBuilder來將xml對象進行解析隨后注入到Configuration對象中最后再將Configuration對象放入注入到SqlSessionFactory對象中完成真正的構建sql工廠。
至此配置文件算是完成了解析并且注入。從讀源碼的過程中我們發現源碼用到了多種設計模式:生成器模式--這個模式的主要目的就是講對象生成過程中的復雜邏輯進行封裝,外部使用者直接調用生成器的方法就可以返回成品對象,降低了代碼的耦合度。以及工廠模式等設計模式。通過理解這些源碼的代碼風格以及設計模式將會學到很多知識。