1. 程式人生 > >mybatis原始碼學習:從SqlSessionFactory到代理物件的生成

mybatis原始碼學習:從SqlSessionFactory到代理物件的生成

[toc] # 一、根據XML配置檔案構建SqlSessionFactory 一、首先讀取類路徑下的配置檔案,獲取其位元組輸入流。 二、建立SqlSessionFactoryBuilder物件,呼叫內部的build方法。`factory = new SqlSessionFactoryBuilder().build(in);` 三、根據位元組輸入流建立XMLConfigBuilder即解析器物件parser。`XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);` ```java public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { //根據位元組輸入流建立XMLConfigBuilder即解析器物件parser XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); //返回的Configuration配置物件作為build的引數 return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } } ``` 四、呼叫parser物件的parse方法,`parser.parse()`,該結果將返回一個Configuration配置物件,作為build方法的引數。 五、parse()方法中,呼叫parseConfiguration方法將Configuration元素下的所有配置資訊封裝進Parser物件的成員Configuration物件之中。 ```java public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; //將configuration的配置資訊一一封裝到configuration中 parseConfiguration(parser.evalNode("/configuration")); return configuration; } ``` 六、其中進行解析xml元素的方式是將通過evalNode方法獲取對應名稱的節點資訊。如:`parseConfiguration(parser.evalNode("/configuration"));`,此時`parser.evalNode("/configuration")`即為Configuration下的所有資訊。 七、parseConfiguration方法相當於將裡面每個元素的資訊都單獨封裝到Configuration中。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200419174956516.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NreV9RaWFvQmFfU3Vt,size_16,color_FFFFFF,t_70) 值得一提的是,我們之後要分析基於代理模式產生dao的代理物件涉及到mappers的封裝,其實也在配置檔案讀取封裝的時候就已經完成,也就是在parseConfiguration方法之中:`mapperElement(root.evalNode("mappers"));`。他的作用就是,讀取我們主配置檔案中``的元素內容,也就是我們配置的對映配置檔案。 ```xml ``` `private void mapperElement(XNode parent)`方法將mappers配置下的資訊獲取,此處獲取我們resources包下的com.smday.dao包名。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200419175025870.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NreV9RaWFvQmFfU3Vt,size_16,color_FFFFFF,t_70) 接著就呼叫了configuration的addMappers方法,其實還是呼叫的是mapperRegistry。 ```java public void addMappers(String packageName) { mapperRegistry.addMappers(packageName); } ``` 讀到這裡,我們就會漸漸瞭解MapperRegistry這個類的職責所在,接著來看,這個類中進行的一些工作,在每次新增mappers的時候,會利用ResolverUtil類查詢類路徑下的該包名路徑下,是否有滿足條件的類,如果有的話,就將Class物件新增進去,否則報錯。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/2020041917504365.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NreV9RaWFvQmFfU3Vt,size_16,color_FFFFFF,t_70) ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200419175051488.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NreV9RaWFvQmFfU3Vt,size_16,color_FFFFFF,t_70) 緊接著,就到了一步比較重要的部分,當然只是我個人覺得,因為第一遍看的時候,我沒有想到,這步居然可以封裝許許多多的重要資訊,我們來看一看: ```java public void addMapper(Class type) { if (type.isInterface()) { //如果已經繫結,則丟擲異常 if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { //將介面類作為鍵,將MapperProxyFactory作為值存入 knownMappers.put(type, new MapperProxyFactory(type)); // 在執行解析器之前新增型別十分重要,否則可能會自動嘗試繫結對映器解析器 // 如果型別已知,則不會嘗試 MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); //解析mapper對映檔案,封裝資訊 parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } } ``` 對映配置檔案的讀取依靠namespace,我們可以通過檢視原始碼發現讀取對映配置檔案的方法是loadXmlResouce(),所以namespace名稱空間至關重要: ```java private void loadXmlResource() { // Spring may not know the real resource name so we check a flag // to prevent loading again a resource twice // this flag is set at XMLMapperBuilder#bindMapperForNamespace // 防止載入兩次,可以發現這句 判斷在許多載入資原始檔的時候出現 if (!configuration.isResourceLoaded("namespace:" + type.getName())) { String xmlResource = type.getName().replace('.', '/') + ".xml"; InputStream inputStream = null; try { inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource); } catch (IOException e) { // ignore, resource is not required } if (inputStream != null) { XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName()); //最終解析 xmlParser.parse(); } } } ``` ```java //xmlPaser.parse() public void parse() { if (!configuration.isResourceLoaded(resource)) { //讀取對映配置檔案資訊的主要程式碼 configurationElement(parser.evalNode("/mapper")); //載入完成將該路徑設定進去,防止再次載入 configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); } ``` ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200419175110858.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NreV9RaWFvQmFfU3Vt,size_16,color_FFFFFF,t_70) 可以看到,對對映檔案解析之後,mappedStatements物件中出現了以下內容: ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200419175130650.png) 至此,主配置檔案和對映配置檔案的配置資訊就已經讀取完畢。 八、最後依據獲得的Configuration物件,建立一個`new DefaultSqlSessionFactory(config)`。 ```java public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); } ``` 總結: - 解析配置檔案的資訊,並儲存在Configuration物件中。 - 返回包含Configuration的DefaultSqlSession物件。 # 二、通過SqlSessionFactory建立SqlSession 一、呼叫SqlSessionFactory物件的openSession方法,其實是呼叫`private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit)`方法,通過引數就可以知道,分別是執行器的型別,事務隔離級別和設定是否自動提交,因此,我們就可以得知,我們在建立SqlSession的時候可以指定這些屬性。 ```java private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { //獲取Environment資訊 final Environment environment = configuration.getEnvironment(); //獲取TransactionFactory資訊 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); //建立Transaction物件 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); //建立執行器物件Executor final Executor executor = configuration.newExecutor(tx, execType); //建立DefaultSqlSession物件並返回 return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } ``` 二、從configuration中獲取environment、dataSource和transactionFactory資訊,建立事務物件Transaction。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200419175352133.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NreV9RaWFvQmFfU3Vt,size_16,color_FFFFFF,t_70) 補充:後續看了一些部落格,說是保證executor不為空,因為defaultExecutorType有可能為空。 三、根據配置資訊,執行器資訊和自動提交資訊建立DefaultSqlSession。 # 三、getMapper獲取動態代理物件 下面這句話意思非常明瞭,就是通過傳入介面型別物件,獲取介面代理物件。 `IUserDao userDao1 = sqlSession1.getMapper(IUserDao.class);` 具體的過程如下: 一、首先,呼叫SqlSession的實現類DefaultSqlSession的getMapper方法,其實是在該方法內呼叫configuration的getMapper方法,將介面類物件以及當前sqlsession物件傳入。 ```java //DefaultSqlSession.java @Override public T getMapper(Class type) { //呼叫configuration的getMapper return configuration.getMapper(type, this); } ``` 二、接著呼叫我們熟悉的mapperRegistry,因為我們知道,在讀取配置檔案,建立sqlSession的時候,介面型別資訊就已經被存入到其內部維護的Map之中。 ```java //Configuration.java public T getMapper(Class type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } ``` ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200419175618941.png) 三、我們來看看getMapper方法具體的實現如何: ```java public T getMapper(Class type, SqlSession sqlSession) { //根據傳入的型別獲取對應的鍵,也就是這個代理工廠 final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { //最終返回的是代理工廠產生的一個例項物件 return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } } ``` 四、緊接著,我們進入MapperProxyFactory,真真實實地發現了建立代理物件的過程。 ```java protected T newInstance(MapperProxy mapperProxy) { //建立MapperProxy代理物件 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { //MapperProxy是代理類, final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } ``` ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/202004191756438