每個現象背後都有其緣由,越離奇的bug越是由不起眼的細節引發,每個bug背後都有框架或程式碼執行的原理和機制所在,解決bug,不僅僅需要去網上查詢,還需要對其背後的原理進行了解和總結。
同事大佬最近在學習並使用Mybatis,他使用Mybatis的MapperScannerConfigurer來進行相關配置,並希望通過yml配置來指定basePackage,mappers等屬性。為此,編寫了自定義的配置類StarterAutoConfiguration
和自定義屬性類TkProperties
,並在初始化MapperScannerConfigurer
時使用TkProperties
中的屬性。但是,事與願違,在初始化MapperScannerConfigurer
時,TkProperties
例項中的屬性死活都是未初始化狀態。
為此,我們花了大量時間探查緣由,最後不得不詢問了另一位大佬,才發現這個離奇問題的背後竟然有著這樣的緣由。
我們首先來看一下大佬關於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
註解宣告瞭該屬性配置的字首,兩個屬性名稱為basePackage
和mappers
。
@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
,比如說可以註冊用來定義BeanFactoryPostProcessor
的BeanDefintion
,比如說我們之前提到的
MapperScannerConfigurer
和ConfigurationClassPostProcessor
。
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}
複製程式碼
MapperScannerConfigurer
的postProcessBeanDefinitionRegistry
主要用來ClassPathMapperScanner
來掃描Mybatis
的Mapper
。ClassPathMapperScanner
繼承了ClassPathBeanDefinitionScanner
,在doScan
方法中獲取了basePackage
指定的包路徑下的所有Mapper
的BeanDefinition
,然後進行註冊。
而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;
}
複製程式碼
我們可以總結一下BeanDefinitionRegistryPostProcessor
,BeanFactoryPostProcessor
和BeanPostProcessor
三個後處理器發揮作用的次序和時機。
由此,我們也能夠理解為什麼MapperScannerConfigurer
初始化時,TkProperties
還沒有初始化,那是因為ConfigurationPropertiesBindingPostProcessor
還沒有初始化,並且也沒有對TkProperties
進行處理。
遇到問題和bug,不要百度一下解決方案處理就結束了,而是要深入瞭解一下背後的機制和原理,希望大家都能夠多多探索更加深入的原理,獲得更多的知識。