記錄Mybatis的配置之謎

ztwindy發表於2019-03-03

 每個現象背後都有其緣由,越離奇的bug越是由不起眼的細節引發,每個bug背後都有框架或程式碼執行的原理和機制所在,解決bug,不僅僅需要去網上查詢,還需要對其背後的原理進行了解和總結。
 同事大佬最近在學習並使用Mybatis,他使用Mybatis的MapperScannerConfigurer來進行相關配置,並希望通過yml配置來指定basePackage,mappers等屬性。為此,編寫了自定義的配置類StarterAutoConfiguration和自定義屬性類TkProperties,並在初始化MapperScannerConfigurer時使用TkProperties中的屬性。但是,事與願違,在初始化MapperScannerConfigurer時,TkProperties例項中的屬性死活都是未初始化狀態。

記錄Mybatis的配置之謎

 為此,我們花了大量時間探查緣由,最後不得不詢問了另一位大佬,才發現這個離奇問題的背後竟然有著這樣的緣由。
 我們首先來看一下大佬關於MapperScannerConfigurer的自定義配置實現。他首先定義了自定義配置類BkStarterAutoConfiguration,使用@EnableConfigurationProperties註解將TkProperties宣告為配置屬性類。

@Configuration
@EnableConfigurationProperties({TkProperties.class})
@AutoConfigureBefore(MybatisAutoConfiguration.class)
public class BkStarterAutoConfiguration {
  @Bean
  @ConditionalOnMissingBean
  @Order(Ordered.HIGHEST_PRECEDENCE)
  public TkProperties tkProperties() {
    return new TkProperties();
  }
}
複製程式碼

 下面是TkProperties的定義,使用@ConfigurationProperties註解宣告瞭該屬性配置的字首,兩個屬性名稱為basePackagemappers

@Data
@ConfigurationProperties(prefix = "tk")
public class TkProperties {
  private String basePackage;
  private String mappers;
}
複製程式碼

MapperConfig是宣告並配置MapperScannerConfigurer例項的配置類,使用被@Bean註解修飾的mapperScannerConfigurer方法來初始化,其方法引數為TkProperties

@Configuration
public class MapperConfig {
  @Bean
  public MapperScannerConfigurer mapperScannerConfigurer(TkProperties tkProperties) {
    MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
    mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
    //使用TkProperties的成員變數來配置mapperScannerConfigurer
    mapperScannerConfigurer.setBasePackage(tkProperties.getBasePackage());
    Properties properties = new Properties();
    properties.setProperty("mappers", tkProperties.getMappers());
    mapperScannerConfigurer.setProperties(properties);
    return mapperScannerConfigurer;
  }
}
複製程式碼

 yml配置檔案如下所示。

---
tk:
  basePackage: cn.remcarpediem.mybatis.dao
  mappers: cn.remcarpediem.mappers.BaseDao
複製程式碼

 程式碼乍看起來一定問題都沒有,但是執行時,在初始化MapperScannerConfigurer例項時,TkProperties例項的屬性死活就是沒有初始化成功。

執行除錯圖

 一定有很多見多識廣的讀者已經知道這個現象背後的原因。“凶手”就是MapperScannerConfigurer實現的介面BeanDefinitionRegistryPostProcessor。具體原因我們還需要慢慢來解釋,因為它涉及了Spring Boot的很多原理。

 首先,BeanDefinitionRegistryPostProcessor介面繼承了BeanFactoryPostProcessor介面,大家一般都對BeanFactoryPostProcessor較為熟悉,它是例項工廠(BeanFactory)的後處理器(PostProcessor),與之類似的還有例項的後處理器(BeanPostProcessor)。BeanFactoryPostProcessor中只定義了一個方法,其將會在ApplicationContext內部的BeanFactory載入完BeanDefinition後,但是在Bean例項化之前進行。所以通常我們可以通過實現該介面來對例項化之前的BeanDefinition進行修改。比如說PropertySourcesPlaceholderConfigurer就實現BeanFactoryPostProcessor介面,用於處理例項中被@Value註解修飾的變數,修改其數值。

public interface BeanFactoryPostProcessor {
	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
複製程式碼

 而BeanDefinitionRegistryPostProcessor介面擴充套件自BeanFactoryPostProcessor,它是BeanDefinitionRegistry的後處理器,它可以在BeanFactoryPostProcessor檢測之前註冊一些特殊的BeanDefinition,比如說可以註冊用來定義BeanFactoryPostProcessorBeanDefintion,比如說我們之前提到的
MapperScannerConfigurerConfigurationClassPostProcessor

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}
複製程式碼

MapperScannerConfigurerpostProcessBeanDefinitionRegistry主要用來ClassPathMapperScanner來掃描MybatisMapperClassPathMapperScanner繼承了ClassPathBeanDefinitionScanner,在doScan方法中獲取了basePackage指定的包路徑下的所有MapperBeanDefinition,然後進行註冊。

 而BeanPostProcessor就是Bean例項的後處理器。每個Bean例項在進行初始化前會呼叫其postProcessBeforeInitialization方法和初始化之後呼叫其postProcessAfterInitialization方法。ConfigurationPropertiesBindingPostProcessor實現了BeanPostProcessor介面,用於處理被@ConfigurationProperties修飾的例項。

public interface BeanPostProcessor {
  Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
  Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
複製程式碼

 我們可以總結一下BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessorBeanPostProcessor三個後處理器發揮作用的次序和時機。

三種後處理器的順序

由此,我們也能夠理解為什麼MapperScannerConfigurer初始化時,TkProperties還沒有初始化,那是因為ConfigurationPropertiesBindingPostProcessor還沒有初始化,並且也沒有對TkProperties進行處理

 遇到問題和bug,不要百度一下解決方案處理就結束了,而是要深入瞭解一下背後的機制和原理,希望大家都能夠多多探索更加深入的原理,獲得更多的知識。

記錄Mybatis的配置之謎

相關文章