答讀者問:BeanFactoryPostProcessor 似乎失效了?
有小伙伴在學(xué)習(xí) Spring 源碼視頻的時(shí)候,看了松哥講的 BeanFactoryPostProcessor 的用法之后,提出了這樣一個(gè)問題:
圖片
圖片
我來跟大家補(bǔ)充一下這個(gè)問題的上下文:
我講了 BeanFactoryPostProcessor,分析了其原理,也講了具體的使用場景,一個(gè)典型的使用場景是我們?cè)?XML 中定義 Bean 的時(shí)候,如果 Bean 的屬性是使用了 properties 文件占位符如 ${db.username} 這種,那么在 BeanFactoryPostProcessor 階段,就會(huì)對(duì)這個(gè)占位符進(jìn)行處理,將其替換成真正的 value。然后我還順手給大家舉了一個(gè)例子,我在 XML 文件中定義 Bean 的時(shí)候,給 Bean 的某一個(gè)屬性設(shè)置 value 為 ^username,然后在 BeanFactoryPostProcessor 中,我將 ^username 改為某一個(gè)字符串。
小伙伴看了松哥講的內(nèi)容之后,也照著寫了一個(gè),就是上面圖片中的代碼,不同的是,他是將 XML 配置改為了 Java 代碼配置,結(jié)果發(fā)現(xiàn)屬性 hok 并未變?yōu)?nbsp;NB,因此有了上述問題。
我覺得這個(gè)問題問的很好,給了小伙伴們一個(gè)從其他方面理解 Spring 的機(jī)會(huì),這也是我前面一直強(qiáng)調(diào)的,這次的 Spring 視頻需要各位小伙伴一起發(fā)力,大家有關(guān)于 Spring 的任何問題都可以提,我負(fù)責(zé)通過源碼來回答你。
問題分析
這個(gè)問題的分析,得先從 BeanDefinition 開始。在講 BeanFactoryPostProcessor 之前,松哥已經(jīng)和小伙伴們分析過 BeanDefinition 了,無論我們是通過 Java 代碼還是通過 XML 文件定義的 Bean 對(duì)象,在解析稱為 Bean 對(duì)象之前,得先解析成為 BeanDefinition,BeanDefinition 則有不同的分類,對(duì)于 XML 文件定義的 Bean,最終解析為 GenericBeanDefinition,而通過 @Bean 注解定義的 Bean 則解析為 ConfigurationClassBeanDefinition。
但是這兩個(gè)的處理原理顯然是有差異的。
對(duì)于 XML 定義的 Bean 來說,很明顯 XML 中的所有屬性都要先解析到 BeanDefinition 中,包括我們?cè)?XML 中配置的 Bean 的各種屬性,這一步是在 Spring 容器 refresh 方法中構(gòu)建 BeanFactory 的時(shí)候完成的(obtainFreshBeanFactory 方法),這一步完成之后,在后面的步驟會(huì)去執(zhí)行容器中所有的 BeanFactoryPostProcessor(invokeBeanFactoryPostProcessors),此時(shí)就會(huì)把前面解析出來的 BeanDefinition 中帶有占位符的屬性給替換過來,最后在 refresh 方法中執(zhí)行 finishBeanFactoryInitialization 方法完成 Bean 的初始化。
按照上面這一套流程順序,占位符被解析成為正常字符串沒什么問題。
但是,如果是 @Bean 注解配置的 Bean,則會(huì)有所差異。
首先,@Bean 注解所標(biāo)記的方法要被解析為一個(gè) ConfigurationClassBeanDefinition,這個(gè)過程本身是通過 ConfigurationClassPostProcessor 來完成的,而 ConfigurationClassPostProcessor 本質(zhì)上其實(shí)就是一個(gè) BeanFactoryPostProcessor,換言之,@Bean 注解標(biāo)記的方法是在 BeanFactoryPostProcessor 中被解析為 ConfigurationClassBeanDefinition 的。ConfigurationClassBeanDefinition 這個(gè) BeanDefinition 主要用來記錄 @Bean 注解所標(biāo)記的方法所屬的對(duì)象、方法的名稱、方法對(duì)象、方法參數(shù)、注解的參數(shù)等等信息,把這些信息記錄下來,將來在初始化 Bean 的時(shí)候,通過反射執(zhí)行目標(biāo)方法就可以了,即方法里邊的內(nèi)容是什么,ConfigurationClassBeanDefinition 其實(shí)并不關(guān)心。
最后則是和 XML 一樣,在 finishBeanFactoryInitialization 方法中完成 Bean 的初始化。
經(jīng)過上面分析,小伙伴們可以看到,通過 @Bean 注解定義的 Bean,我們?yōu)閷傩再x值是在方法內(nèi)部完成的,這些方法內(nèi)部的邏輯其實(shí)并未被解析到 BeanDefinition 中,顯然也沒有必要把方法內(nèi)部的邏輯解析到 BeanDefinition 上去,因此,通過 @Bean 注解定義的 Bean,如果屬性中使用了占位符,是無法通過 BeanFactoryPostProcessor 自動(dòng)解析的。
好啦,現(xiàn)在小伙伴提出的問題大家伙都明白了吧?