mybatis源碼學習:從SqlSessionFactory到代理對象的生成

一、根據XML配置文件構建SqlSessionFactory

一、首先讀取類路徑下的配置文件,獲取其位元組輸入流。

二、創建SqlSessionFactoryBuilder對象,調用內部的build方法。factory = new SqlSessionFactoryBuilder().build(in);

三、根據位元組輸入流創建XMLConfigBuilder即解析器對象parser。XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

  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對象之中。

  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中。

在這裡插入圖片描述

值得一提的是,我們之後要分析基於代理模式產生dao的代理對象涉及到mappers的封裝,其實也在配置文件讀取封裝的時候就已經完成,也就是在parseConfiguration方法之中:mapperElement(root.evalNode("mappers"));。他的作用就是,讀取我們主配置文件中<mappers>的元素內容,也就是我們配置的映射配置文件。

    <!-- 配置映射文件的位置 -->
    <mappers>
        <package name="com.smday.dao"></package>
    </mappers>

private void mapperElement(XNode parent)方法將mappers配置下的資訊獲取,此處獲取我們resources包下的com.smday.dao包名。

在這裡插入圖片描述

接著就調用了configuration的addMappers方法,其實還是調用的是mapperRegistry。

  public void addMappers(String packageName) {
    mapperRegistry.addMappers(packageName);
  }

讀到這裡,我們就會漸漸了解MapperRegistry這個類的職責所在,接著來看,這個類中進行的一些工作,在每次添加mappers的時候,會利用ResolverUtil類查找類路徑下的該包名路徑下,是否有滿足條件的類,如果有的話,就將Class對象添加進去,否則報錯。

在這裡插入圖片描述
在這裡插入圖片描述

緊接著,就到了一步比較重要的部分,當然只是我個人覺得,因為第一遍看的時候,我沒有想到,這步居然可以封裝許許多多的重要資訊,我們來看一看:

  public <T> void addMapper(Class<T> 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<T>(type));
        // 在運行解析器之前添加類型十分重要,否則可能會自動嘗試綁定映射器解析器
        // 如果類型已知,則不會嘗試
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        //解析mapper映射文件,封裝資訊
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

映射配置文件的讀取依靠namespace,我們可以通過查看源碼發現讀取映射配置文件的方法是loadXmlResouce(),所以namespace命名空間至關重要:

  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();
      }
    }
  }
  //xmlPaser.parse()
  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      //讀取映射配置文件資訊的主要程式碼
      configurationElement(parser.evalNode("/mapper"));
      //載入完成將該路徑設置進去,防止再次載入
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

在這裡插入圖片描述

可以看到,對映射文件解析之後,mappedStatements對象中出現了以下內容:

在這裡插入圖片描述

至此,主配置文件和映射配置文件的配置資訊就已經讀取完畢。

八、最後依據獲得的Configuration對象,創建一個new DefaultSqlSessionFactory(config)

  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的時候可以指定這些屬性。

  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。

在這裡插入圖片描述

補充:後續看了一些部落格,說是保證executor不為空,因為defaultExecutorType有可能為空。

三、根據配置資訊,執行器資訊和自動提交資訊創建DefaultSqlSession。

三、getMapper獲取動態代理對象

下面這句話意思非常明了,就是通過傳入介面類型對象,獲取介面代理對象。

IUserDao userDao1 = sqlSession1.getMapper(IUserDao.class);

具體的過程如下:

一、首先,調用SqlSession的實現類DefaultSqlSession的getMapper方法,其實是在該方法內調用configuration的getMapper方法,將介面類對象以及當前sqlsession對象傳入。

  //DefaultSqlSession.java
  @Override
  public <T> T getMapper(Class<T> type) {
    //調用configuration的getMapper
    return configuration.<T>getMapper(type, this);
  }

二、接著調用我們熟悉的mapperRegistry,因為我們知道,在讀取配置文件,創建sqlSession的時候,介面類型資訊就已經被存入到其內部維護的Map之中。

  //Configuration.java
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    
    return mapperRegistry.getMapper(type, sqlSession);
  }

在這裡插入圖片描述
三、我們來看看getMapper方法具體的實現如何:

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //根據傳入的類型獲取對應的鍵,也就是這個代理工廠
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) 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,真真實實地發現了創建代理對象的過程。

  protected T newInstance(MapperProxy<T> mapperProxy) {
    //創建MapperProxy代理對象
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    //MapperProxy是代理類,
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

在這裡插入圖片描述

Tags: