記錄 Mybatis 的配置之謎
每個現象背後都有其緣由,越離奇的bug越是由不起眼的細節引發,每個bug背後都有框架或程式碼執行的原理和機制所在,解決bug,不僅僅需要去網上查詢,還需要對其背後的原理進行了解和總結。 同事大佬最近在學習並使用Mybatis,他使用Mybatis的MapperScannerConfigurer來進行相關配置,並希望通過yml配置來指定basePackage,mappers等屬性。為此,編寫了自定義的配置類 StarterAutoConfiguration
和自定義屬性類 TkProperties
,並在初始化 MapperScannerConfigurer
時使用 TkProperties
中的屬性。但是,事與願違,在初始化 MapperScannerConfigurer
時, TkProperties
例項中的屬性死活都是未初始化狀態。
為此,我們花了大量時間探查緣由,最後不得不詢問了另一位大佬,才發現這個離奇問題的背後竟然有著這樣的緣由。 我們首先來看一下大佬關於 MapperScannerConfigurer
的自定義配置實現。他首先定義了自定義配置類 BkStarterAutoConfiguration
,使用 @EnableConfigurationProperties
註解將 TkProperties
宣告為配置屬性類。
下面是 TkProperties
的定義,使用 @ConfigurationProperties
註解聲明瞭該屬性配置的字首,兩個屬性名稱為 basePackage
和 mappers
。
MapperConfig
是宣告並配置 MapperScannerConfigurer
例項的配置類,使用被 @Bean
註解修飾的 mapperScannerConfigurer
方法來初始化,其方法引數為 TkProperties
。
yml配置檔案如下所示。
程式碼乍看起來一定問題都沒有,但是執行時,在初始化MapperScannerConfigurer例項時,TkProperties例項的屬性死活就是沒有初始化成功。
一定有很多見多識廣的讀者已經知道這個現象背後的原因。“凶手”就是 MapperScannerConfigurer
實現的介面 BeanDefinitionRegistryPostProcessor
。具體原因我們還需要慢慢來解釋,因為它涉及了Spring Boot的很多原理。
首先, BeanDefinitionRegistryPostProcessor
介面繼承了 BeanFactoryPostProcessor
介面,大家一般都對 BeanFactoryPostProcessor
較為熟悉,它是例項工廠(BeanFactory)的後處理器(PostProcessor),與之類似的還有例項的後處理器(BeanPostProcessor)。 BeanFactoryPostProcessor
中只定義了一個方法,其將會在 ApplicationContext
內部的 BeanFactory
載入完 BeanDefinition
後,但是在Bean例項化之前進行。所以通常我們可以通過實現該介面來對例項化之前的 BeanDefinition
進行修改。比如說 PropertySourcesPlaceholderConfigurer
就實現 BeanFactoryPostProcessor
介面,用於處理例項中被 @Value
註解修飾的變數,修改其數值。
而 BeanDefinitionRegistryPostProcessor
介面擴充套件自 BeanFactoryPostProcessor
,它是 BeanDefinitionRegistry
的後處理器,它可以在 BeanFactoryPostProcessor
檢測之前註冊一些特殊的 BeanDefinition
,比如說可以註冊用來定義 BeanFactoryPostProcessor
的 BeanDefintion
,比如說我們之前提到的 MapperScannerConfigurer
和 ConfigurationClassPostProcessor
。
MapperScannerConfigurer
的 postProcessBeanDefinitionRegistry
主要用來 ClassPathMapperScanner
來掃描 Mybatis
的 Mapper
。 ClassPathMapperScanner
繼承了 ClassPathBeanDefinitionScanner
,在 doScan
方法中獲取了 basePackage
指定的包路徑下的所有 Mapper
的 BeanDefinition
,然後進行註冊。
而 BeanPostProcessor
就是Bean例項的後處理器。每個Bean例項在進行初始化前會呼叫其 postProcessBeforeInitialization
方法和初始化之後呼叫其 postProcessAfterInitialization
方法。 ConfigurationPropertiesBindingPostProcessor
實現了 BeanPostProcessor
介面,用於處理被 @ConfigurationProperties
修飾的例項。
我們可以總結一下 BeanDefinitionRegistryPostProcessor
, BeanFactoryPostProcessor
和 BeanPostProcessor
三個後處理器發揮作用的次序和時機。
由此,我們也能夠理解為什麼 MapperScannerConfigurer初始化時, TkProperties還沒有初始化,那是因為 ConfigurationPropertiesBindingPostProcessor還沒有初始化,並且也沒有對 TkProperties進行處理。
遇到問題和bug,不要百度一下解決方案處理就結束了,而是要深入瞭解一下背後的機制和原理,希望大家都能夠多多探索更加深入的原理,獲得更多的知識。