Spring源碼學習(十):Spring與Mybatis整合原理

 

目錄

1.關鍵類的加載

1.1 SqlSessionFactory的加載

1.2 SqlSessionTemplate的加載

2. Mapper的自動綁定

2.1 processPropertyPlaceHolders

2.2 Mapper掃描

2.3 Mapper的後處理


學習Mybatis(7):Mybatis運行原理源碼分析 其實可以知道Mybatis運行的原理,不難想象Spring整合Mybatis,就是依靠將SqlSessionFactory等組件封裝爲Bean來實現的。

這裏還是根據學習Mybatis(4):結合Spring Boot的例子。

1.關鍵類的加載

Spring Boot使用Mybatis時,引入的是starter包,同時也會帶進來一個autoconfigure依賴。就是該依賴完成的Mybatis自動配置。從spring.factories可以看出,其核心類是MybatisAutoConfiguration,這是一個InitializingBean。不過我們更關注的是兩個方法和一個類:

  • sqlSessionFactory(DataSource dataSource)
  • sqlSessionTemplate(SqlSessionFactory sqlSessionFactory)
  • MapperScannerRegistrarNotFoundConfiguration

前兩個是方法,被@Bean註解,最後是內部類,會在沒有加載MapperFactoryBean、MapperScannerConfigurer時加載,且Import另一個內部類AutoConfiguredMapperScannerRegistrar完成Mapper的掃描。

首先看第一個方法

1.1 SqlSessionFactory的加載

首先會創建一個SqlSessionFactoryBean,這是一個InitializingBean,也是FactoryBean。sqlSessionFactory方法的剩餘部分就是將配置文件的配置值賦給SqlSessionFactoryBean,最後調用getObject()方法獲得SqlSessionFactory對象。

實際調用了afterPropertiesSet方法,核心邏輯是調用buildSqlSessionFactory(),整個方法大概160行,所以不上源碼了。

方法內通過一系列的if塊,判斷各屬性是否爲空,不爲空則添加到Configuration對象中,最後和獨立使用Mybatis時一樣,調用sqlSessionFactoryBuilder.build(targetConfiguration)創建一個SqlSessionFactory對象。

這裏的各屬性其實就是mybatis_config.xml配置文件中的內容,如果是XML形式配置的Spring,也可以在註冊SqlSessionFactoryBean時定義,不過在Spring Boot中,都是剛剛從properties或yaml文件中加載的。

在示例中,properties文件裏有一條mybatis.mapper-locations配置會在經過解析後賦值給SqlSessionFactoryBean,結合學習Mybatis(7):Mybatis運行原理源碼分析,可以看到在XMLMapperBuilder類中,會通過反射將mapper配置文件中namespace屬性加載爲類,並且通過Configuration#addMapper加載:

    private void bindMapperForNamespace() {
        String namespace = this.builderAssistant.getCurrentNamespace();
        if (namespace != null) {
            Class boundType = null;

            try {
                boundType = Resources.classForName(namespace);
            } catch (ClassNotFoundException var4) {
            }

            if (boundType != null && !this.configuration.hasMapper(boundType)) {
                this.configuration.addLoadedResource("namespace:" + namespace);
                this.configuration.addMapper(boundType);
            }
        }

    }

1.2 SqlSessionTemplate的加載

將ExecutorType(沒配置的話就不傳,反正Configuration類有默認值)和SqlSessionFactory傳入構造函數,new一個出來。不過構造時,會創建一個SqlSession的代理,並且由內部類SqlSessionInterceptor提供強化。其實主要就是提供異常處理(錯誤解釋)功能,還有非事務會話的自動提交、會話自動關閉等。

此外就是代理創建時,是會把Session存進一個Holder裏,類似於JdbcTemplate的實現,Session的獲取、釋放,實際都是引用數的加減,並不會真的關閉會話。

2. Mapper的自動綁定

從示例代碼中,不難看出,Mapper是被自動綁定進Service中的,也就是說,Mapper也是以Bean的形式完成的註冊。如果是在XML形式配置的Spring中,其實我們可以手動註冊MapperFactoryBean來加載Mapper,也可以在Mapper數量很多的時候開啓自動掃描。

但是在Spring Boot中,根據前面的分析,Mapper還是直接反射加載的,所以還需要將其包裝爲Bean,並進行自動綁定。這就需要靠AutoConfiguredMapperScannerRegistrar,會註冊MapperScannerConfigurer類以自動掃描、創建映射器。

MapperScannerConfigurer是一個BeanDefinitionRegistryPostProcessor,會在註冊後調用postProcessBeanDefinitionRegistry方法。

2.1 processPropertyPlaceHolders

postProcessBeanDefinitionRegistry方法會先判斷processPropertyPlaceHolders是否爲true,是則調用同名方法。它會嘗試獲取PropertyResourceConfigurer類型的Bean,然後讀取properties配置文件,再把裏面的basePackage、sqlSessionFactoryBeanName、sqlSessionTemplateBeanName等配置替換到MapperScannerConfigurer中,其實就是XML配置文件從properties中佔位引用配置值的功能。

之所以要在這做處理,從代碼這一段可以看出原因:

Iterator var4 = prcs.values().iterator();
while(var4.hasNext()) {
    PropertyResourceConfigurer prc = (PropertyResourceConfigurer)var4.next();
    prc.postProcessBeanFactory(factory);
}

其實就是因爲這時候PropertyResourceConfigurer還沒加載完,如果不手動地觸發一下postProcessBeanFactory,就沒辦法完成屬性的替換。

2.2 Mapper掃描

首先創建了一個ClassPathMapperScanner,然後設定了一大堆屬性:

ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(this.lazyInitialization)) {
    scanner.setLazyInitialization(Boolean.valueOf(this.lazyInitialization));
}

然後是註冊一些過濾器,包括:接受被@Mapper註解的類、接受指定接口的實現類、排除名爲package-info的類,如果沒有任何類被@Mapper註解,也沒有任何實現指定接口的類,那麼就掃描所有接口。

下一步就是開始掃描,主要是doScan方法完成的,它會遍歷傳入的所有路徑,然後調用findCandidateComponents來篩選候選Bean,並進行加載。首先是拼接路徑,然後加載所有文件:

String packageSearchPath = "classpath*:" + this.resolveBasePackage(basePackage) + '/' + this.resourcePattern;
Resource[] resources = this.getResourcePatternResolver().getResources(packageSearchPath);

然後遍歷這些文件,使用isCandidateComponent方法看它們是否符合要求,其實就是用上面註冊的過濾器進行過濾。將符合要求的添加到集合中,並最終返回。

遍歷這些候選Bean,配置其scope屬性:

BeanDefinition candidate = (BeanDefinition)var8.next();
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());

根據Bean名稱配置自動綁定:

beanDefinition.applyDefaults(this.beanDefinitionDefaults);
if (this.autowireCandidatePatterns != null) {
    beanDefinition.setAutowireCandidate(PatternMatchUtils.simpleMatch(this.autowireCandidatePatterns, beanName));
}

處理@Lazy、@Primary、@DependsOn等註解。然後檢查候選Bean是否已經註冊過了,沒有的話就進行註冊,如果Bean是用於生成代理(scopedProxyMode屬性不爲NO),則創建一個代理的BeanDefinition用於註冊。

到這裏,Mapper就完成掃描和自動綁定了。但是顯然還差了一步,Mapper是自動綁定了,但是還沒有和SqlSessionFactory建立聯繫,這樣Mybatis肯定也是沒法正常運作的,所以還需要繼續處理,這裏調用了processBeanDefinitions方法。

2.3 Mapper的後處理

實際上,SqlSessionFactory在Mapper Bean掃描的時候已經傳給掃描器了,processPropertyPlaceHolders方法已經設定了sqlSessionFactoryBeanName值,並在postProcessBeanDefinitionRegistry方法中傳給了ClassPathMapperScanner。

在上面Mapper Bean完成註冊和綁定後,ClassPathMapperScanner就開始對BeanDefinition進行後處理,實質就是將它們從原本的類型,轉換成MapperFactoryBean,並且添加sqlSessionFactory、sqlSessionTemplate(這兩個只能存在其一,不能並存)等屬性:

      definition.getPropertyValues().add("addToConfig", this.addToConfig);

      boolean explicitFactoryUsed = false;
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
        definition.getPropertyValues().add("sqlSessionFactory",
            new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionFactory != null) {
        definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
        explicitFactoryUsed = true;
      }

      if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
        if (explicitFactoryUsed) {
          LOGGER.warn(
              () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate",
            new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionTemplate != null) {
        if (explicitFactoryUsed) {
          LOGGER.warn(
              () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }

如果sqlSessionFactory和sqlSessionTemplate都沒配置,那就只能根據類型來進行自動綁定了:

if (!explicitFactoryUsed) {
    LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
    definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}

最後設置了一下懶加載。到這裏,Mapper類就和Mybatis搭上了關係,在MapperFactoryBean中,可以看到如下方法:

public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
}

這是FactoryBean的接口方法,當Spring容器使用FactoryBean產生Bean對象時,就可以拿到Mapper實現類對象了。前面提到,在掃描到Mapper Bean的定義後,就配置了自動綁定信息,後面Bean類型又被改成MapperFactoryBean,所以實際自動綁定時,就會通過getObject()拿到真實的Mapper實現類對象。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章