java工廠實(shí)例BeanFactoryPostProcessor和BeanPostProcessor區(qū)別分析
引言
研究 Spring 源碼的小伙伴可能會(huì)發(fā)現(xiàn),Spring 源碼中有很多名稱特別相近的 Bean,我就不挨個(gè)舉例了,今天我是想和小伙伴們聊一聊 Spring 中 BeanFactoryPostProcessor 和 BeanPostProcessor 兩個(gè)處理器的區(qū)別。
我將從以下幾個(gè)方面來和小伙伴們分享。
1. 區(qū)別
這兩個(gè)接口說白了都是 Spring 在初始化 Bean 時(shí)對(duì)外暴露的擴(kuò)展點(diǎn),因?yàn)?Spring 框架提供的功能不一定能夠滿足我們所有的需求,有的時(shí)候我們需要對(duì)其進(jìn)行擴(kuò)展,那么這兩個(gè)接口就是用來做擴(kuò)展功能的。
其實(shí)不用看源碼,單純從字面上看,大家應(yīng)該也能理解個(gè)差不多:
BeanFactoryPostProcessor 是針對(duì) BeanFactory 的處理器。
BeanPostProcessor 則是針對(duì) Bean 的處理器。
我們先來看下 BeanFactoryPostProcessor 接口:
@FunctionalInterface public?interface?BeanFactoryPostProcessor?{ ?void?postProcessBeanFactory(ConfigurableListableBeanFactory?beanFactory)?throws?BeansException; }
可以看到,這里的參數(shù)實(shí)際上就是一個(gè) BeanFactory,在這個(gè)地方,我們可以對(duì) BeanFactory 進(jìn)行修改,重新進(jìn)行定制。例如可以修改一個(gè) Bean 的作用域,可以修改屬性值等。
再來看看 BeanPostProcessor 接口:
public?interface?BeanPostProcessor?{ ?@Nullable ?default?Object?postProcessBeforeInitialization(Object?bean,?String?beanName)?throws?BeansException?{ ??return?bean; ?} ?@Nullable ?default?Object?postProcessAfterInitialization(Object?bean,?String?beanName)?throws?BeansException?{ ??return?bean; ?} }
可以看到,這里是兩個(gè)方法,這兩個(gè)方法都有一個(gè) bean 對(duì)象,說明這里被觸發(fā)的時(shí)候,Spring 已經(jīng)將 Bean 初始化了,然后才會(huì)觸發(fā)這里的兩個(gè)方法,我們可以在這里對(duì)已經(jīng)到手的 Bean 進(jìn)行額外的處理。其中:
- postProcessBeforeInitialization:這個(gè)方法在
InitializingBean#afterPropertiesSet
和init-method
方法之前被觸發(fā)。 - postProcessAfterInitialization:這個(gè)方法在
InitializingBean#afterPropertiesSet
和init-method
方法之后被觸發(fā)。
總結(jié)一下:
在 Spring 中,BeanFactoryPostProcessor 和 BeanPostProcessor 是兩個(gè)不同的接口,它們?cè)?Bean 的生命周期中扮演不同的角色。
- BeanFactoryPostProcessor 接口用于在 Bean 工廠實(shí)例化 Bean 之前對(duì) Bean 的定義進(jìn)行修改。它可以讀取和修改 Bean 的定義元數(shù)據(jù),例如修改 Bean 的屬性值、添加額外的配置信息等。BeanFactoryPostProcessor 在 Bean 實(shí)例化之前執(zhí)行,用于對(duì) Bean 的定義進(jìn)行預(yù)處理。
- BeanPostProcessor 接口用于在 Bean 實(shí)例化后對(duì) Bean 進(jìn)行增強(qiáng)或修改。它可以在 Bean 的初始化過程中對(duì) Bean 進(jìn)行后處理,例如對(duì) Bean 進(jìn)行代理、添加額外的功能等。BeanPostProcessor 在 Bean 實(shí)例化完成后執(zhí)行,用于對(duì) Bean 實(shí)例進(jìn)行后處理。
一言以蔽之,BeanFactoryPostProcessor 主要用于修改 Bean 的定義,而 BeanPostProcessor 主要用于增強(qiáng)或修改 Bean 的實(shí)例。
2. 代碼實(shí)踐
2.1 BeanFactoryPostProcessor
BeanFactoryPostProcessor 在 Spring 容器中有一個(gè)非常典型的應(yīng)用。
當(dāng)我們?cè)?Spring 容器中配置數(shù)據(jù)源的時(shí)候,一般都是按照下面這樣的方式進(jìn)行配置的。
首先創(chuàng)建 db.properties,將數(shù)據(jù)源各種信息寫入進(jìn)去:
db.username=root db.password=123 db.url=jdbc:mysql:///db01?serverTimezone=Asia/Shanghai
然后在 Spring 的配置文件中,首先把這個(gè)配置文件加載進(jìn)來,然后就可以在 Spring Bean 中去使用對(duì)應(yīng)的值了,如下:
<context:property-placeholder?location="classpath:db.properties"/> <bean?class="com.alibaba.druid.pool.DruidDataSource"?id="dataSource"> ????<property?name="username"?value="${db.username}"/> ????<property?name="password"?value="${db.password}"/> ????<property?name="url"?value="${db.url}"/> </bean>
但是大家知道,對(duì)于 DruidDataSource 來說,毫無疑問,它要的是具體的 username、password 以及 url,而上面的配置很明顯中間還有一個(gè)轉(zhuǎn)換的過程,即把 ${db.username}
、${db.password}
以及 ${db.url}
轉(zhuǎn)為具體對(duì)應(yīng)的值。那么這個(gè)轉(zhuǎn)換是怎么實(shí)現(xiàn)的呢?
這就得分析 <context:property-placeholder location="classpath:db.properties"/>
配置了,小伙伴們知道,這個(gè)配置實(shí)際上是一個(gè)簡(jiǎn)化的配置,點(diǎn)擊去可以看到真正配置的 Bean 是 PropertySourcesPlaceholderConfigurer
,而 PropertySourcesPlaceholderConfigurer
恰好就是 BeanFactoryPostProcessor
的子類,我們來看下這里是如何重寫 postProcessBeanFactory 方法的:
源碼比較長(zhǎng),松哥這里把一些關(guān)鍵部分列出來和小伙伴們展示:
@Override public?void?postProcessBeanFactory(ConfigurableListableBeanFactory?beanFactory)?throws?BeansException?{ ?if?(this.propertySources?==?null)?{ ?????//這里主要是如果沒有加載到?properties?文件,就會(huì)嘗試從環(huán)境中加載 ?} ????//這個(gè)就是具體的屬性轉(zhuǎn)換的方法了 ?processProperties(beanFactory,?new?PropertySourcesPropertyResolver(this.propertySources)); ?this.appliedPropertySources?=?this.propertySources; } /** ?*?這個(gè)屬性轉(zhuǎn)換方法中,對(duì)配置文件又做了一些預(yù)處理,最后調(diào)用?doProcessProperties?方法處理屬性 ?*/ protected?void?processProperties(ConfigurableListableBeanFactory?beanFactoryToProcess, ??final?ConfigurablePropertyResolver?propertyResolver)?throws?BeansException?{ ?propertyResolver.setPlaceholderPrefix(this.placeholderPrefix); ?propertyResolver.setPlaceholderSuffix(this.placeholderSuffix); ?propertyResolver.setValueSeparator(this.valueSeparator); ?StringValueResolver?valueResolver?=?strVal?->?{ ??String?resolved?=?(this.ignoreUnresolvablePlaceholders?? ????propertyResolver.resolvePlaceholders(strVal)?: ????propertyResolver.resolveRequiredPlaceholders(strVal)); ??if?(this.trimValues)?{ ???resolved?=?resolved.trim(); ??} ??return?(resolved.equals(this.nullValue)???null?:?resolved); ?}; ?doProcessProperties(beanFactoryToProcess,?valueResolver); } protected?void?doProcessProperties(ConfigurableListableBeanFactory?beanFactoryToProcess, ??StringValueResolver?valueResolver)?{ ?BeanDefinitionVisitor?visitor?=?new?BeanDefinitionVisitor(valueResolver); ?String[]?beanNames?=?beanFactoryToProcess.getBeanDefinitionNames(); ?for?(String?curName?:?beanNames)?{ ??//?Check?that?we're?not?parsing?our?own?bean?definition, ??//?to?avoid?failing?on?unresolvable?placeholders?in?properties?file?locations. ??if?(!(curName.equals(this.beanName)?&&?beanFactoryToProcess.equals(this.beanFactory)))?{ ???BeanDefinition?bd?=?beanFactoryToProcess.getBeanDefinition(curName); ???try?{ ????visitor.visitBeanDefinition(bd); ???} ???catch?(Exception?ex)?{ ????throw?new?BeanDefinitionStoreException(bd.getResourceDescription(),?curName,?ex.getMessage(),?ex); ???} ??} ?} ?//?New?in?Spring?2.5:?resolve?placeholders?in?alias?target?names?and?aliases?as?well. ?beanFactoryToProcess.resolveAliases(valueResolver); ?//?New?in?Spring?3.0:?resolve?placeholders?in?embedded?values?such?as?annotation?attributes. ?beanFactoryToProcess.addEmbeddedValueResolver(valueResolver); }
上面第三個(gè) doProcessProperties 方法我要稍微和小伙伴們說兩句:
使用 PropertySourcesPlaceholderConfigurer 對(duì)配置中的占位符進(jìn)行處理,雖然我們只是在 DruidDataSource 中用到了相關(guān)變量,但是系統(tǒng)在處理的時(shí)候,除了當(dāng)前這個(gè)配置類之外,其他的 Bean 都要處理(因?yàn)槟憧梢栽谌我?Bean 中注入那三個(gè)變量)。
這就是 BeanFactoryPostProcessor 一個(gè)經(jīng)典實(shí)踐,即在 Bean 初始化之前,把 Bean 定義時(shí)候的一些占位符給改過來。
2.2 照貓畫虎
上面的源碼看完了,如果小伙伴們還覺得不過癮,我們自己也來寫一個(gè)試試。
我自己的需求是這樣,假設(shè)我配置 Bean 的時(shí)候,按照下面這種方式來配置:
<bean?class="org.javaboy.bean.User"> ????<property?name="username"?value="^username"/> </bean>
這里的 ^username
是我自定義的一個(gè)特殊的占位符,這個(gè)占位符表示 javaboy,我希望最終從 Spring 容器中拿到的 User Bean 的 username 屬性值是 javaboy。
為了實(shí)現(xiàn)這個(gè)需求,我可以自定義一個(gè) BeanFactoryPostProcessor,如下:
public?class?MyPropBeanFactoryPostProcessor?implements?BeanFactoryPostProcessor?{ ????@Override ????public?void?postProcessBeanFactory(ConfigurableListableBeanFactory?beanFactory)?throws?BeansException?{ ????????String[]?beanDefinitionNames?=?beanFactory.getBeanDefinitionNames(); ????????for?(String?beanDefinitionName?:?beanDefinitionNames)?{ ????????????BeanDefinition?beanDefinition?=?beanFactory.getBeanDefinition(beanDefinitionName); ????????????BeanDefinitionVisitor?beanDefinitionVisitor?=?new?BeanDefinitionVisitor(strVal?->?{ ????????????????if?("^username".equals(strVal))?{ ????????????????????return?"javaboy666"; ????????????????} ????????????????return?strVal; ????????????}); ????????????beanDefinitionVisitor.visitBeanDefinition(beanDefinition); ????????} ????} }
這個(gè) Bean 基本上是照著前面 2.1 小節(jié)的 Bean 來寫的。我跟大家來大致說一下我的邏輯:
- 首先獲取到所有的 Bean 定義對(duì)象,然后進(jìn)行遍歷。
- 遍歷的時(shí)候創(chuàng)建一個(gè) BeanDefinitionVisitor 對(duì)象,這個(gè)對(duì)象需要一個(gè)字符解析器,也就是 lambda 表達(dá)式那一段,我這里就是說,如果傳進(jìn)來的字符串是
^username
,那么我就返回 javaboy,如果傳進(jìn)來其他值,那我原封不動(dòng),不做修改。 - 最后調(diào)用 visitBeanDefinition 方法去重新設(shè)置一下 Bean 的定義。
上面這幾行代碼基本上就是照著 2.1 小節(jié)敲的,最后,我們把 MyPropBeanFactoryPostProcessor 注冊(cè)到 Spring 容器中就行了:
<bean?class="org.javaboy.bean.User"> ????<property?name="username"?value="^username"/> </bean> <bean?class="org.javaboy.bean.MyPropBeanFactoryPostProcessor"/>
看下執(zhí)行效果:
2.3 BeanPostProcessor
BeanPostProcessor 主要是對(duì)一個(gè)已經(jīng)初始化的 Bean 做一些額外的配置,這個(gè)接口中包含兩個(gè)方法,執(zhí)行時(shí)間如下圖:
我寫一個(gè)簡(jiǎn)單例子我們來驗(yàn)證下:
public?class?UserService?implements?InitializingBean?{ ????public?UserService()?{ ????????System.out.println("UserService>Constructor"); ????} ????public?void?init()?{ ????????System.out.println("UserService>init"); ????} ????@Override ????public?void?afterPropertiesSet()?throws?Exception?{ ????????System.out.println("UserService>afterPropertiesSet"); ????} }
再開發(fā)一個(gè) BeanPostProcessor:
public?class?MyBeanPostProcessor?implements?BeanPostProcessor?{ ????@Override ????public?Object?postProcessBeforeInitialization(Object?bean,?String?beanName)?throws?BeansException?{ ????????System.out.println("postProcessBeforeInitialization:beanName"+beanName+";beanClass:"+bean.getClass()); ????????return?BeanPostProcessor.super.postProcessBeforeInitialization(bean,?beanName); ????} ????@Override ????public?Object?postProcessAfterInitialization(Object?bean,?String?beanName)?throws?BeansException?{ ????????System.out.println("postProcessAfterInitialization:beanName"+beanName+";beanClass:"+bean.getClass()); ????????return?BeanPostProcessor.super.postProcessAfterInitialization(bean,?beanName); ????} }
然后我們將這兩個(gè) Bean 都注冊(cè)到 Spring 容器中:
<bean?class="org.javaboy.bean.MyBeanPostProcessor"/> <bean?class="org.javaboy.bean.UserService"?id="us"?init-method="init"> </bean>
最后啟動(dòng)容器,來看下控制臺(tái)打印的內(nèi)容:
可以看到,跟我們所預(yù)想的是一樣的。在 MyBeanPostProcessor 中,第一個(gè)參數(shù)其實(shí)就是已經(jīng)初始化的 Bean 了,如果想在這里針對(duì) Bean 做任何修改都是可以的。
2.4 典型應(yīng)用
BeanPostProcessor 其實(shí)有很多經(jīng)典的應(yīng)用,我在寫文章的時(shí)候,想到一個(gè)地方,就是我們?cè)?SpringMVC 中做數(shù)據(jù)驗(yàn)證的時(shí)候,往往只需要加幾個(gè)注解就可以了(對(duì)此不熟悉的小伙伴可以在公眾號(hào)后臺(tái)回復(fù) ssm 有松哥錄制的 ssm 入門視頻),那么這個(gè)注解是在哪里進(jìn)行的校驗(yàn)的呢?就是 BeanPostProcessor,我們來看一眼源碼:
public?class?BeanValidationPostProcessor?implements?BeanPostProcessor,?InitializingBean?{ ?@Nullable ?private?Validator?validator; ?private?boolean?afterInitialization?=?false; ?public?void?setAfterInitialization(boolean?afterInitialization)?{ ??this.afterInitialization?=?afterInitialization; ?} ?@Override ?public?void?afterPropertiesSet()?{ ??if?(this.validator?==?null)?{ ???this.validator?=?Validation.buildDefaultValidatorFactory().getValidator(); ??} ?} ?@Override ?public?Object?postProcessBeforeInitialization(Object?bean,?String?beanName)?throws?BeansException?{ ??if?(!this.afterInitialization)?{ ???doValidate(bean); ??} ??return?bean; ?} ?@Override ?public?Object?postProcessAfterInitialization(Object?bean,?String?beanName)?throws?BeansException?{ ??if?(this.afterInitialization)?{ ???doValidate(bean); ??} ??return?bean; ?} ?/** ??*?Perform?validation?of?the?given?bean. ??*?@param?bean?the?bean?instance?to?validate ??*?@see?jakarta.validation.Validator#validate ??*/ ?protected?void?doValidate(Object?bean)?{ ????????//... ?} }
這段代碼其實(shí)很好懂,可以看到,我們可以控制是在具體是在 postProcessBeforeInitialization 還是 postProcessAfterInitialization 方法中進(jìn)行數(shù)據(jù)校驗(yàn)。
好了,現(xiàn)在小伙伴們應(yīng)該搞懂 BeanFactoryPostProcessor 和 BeanPostProcessor 的區(qū)別了吧?
3. 小結(jié)
我再把前文的小結(jié)內(nèi)容拿過來,小伙伴們現(xiàn)在看是不是更清晰了?
在 Spring 中,BeanFactoryPostProcessor 和 BeanPostProcessor 是兩個(gè)不同的接口,它們?cè)?Bean 的生命周期中扮演不同的角色。
- BeanFactoryPostProcessor 接口用于在 Bean 工廠實(shí)例化 Bean 之前對(duì) Bean 的定義進(jìn)行修改。它可以讀取和修改 Bean 的定義元數(shù)據(jù),例如修改 Bean 的屬性值、添加額外的配置信息等。BeanFactoryPostProcessor 在 Bean 實(shí)例化之前執(zhí)行,用于對(duì) Bean 的定義進(jìn)行預(yù)處理。
- BeanPostProcessor 接口用于在 Bean 實(shí)例化后對(duì) Bean 進(jìn)行增強(qiáng)或修改。它可以在 Bean 的初始化過程中對(duì) Bean 進(jìn)行后處理,例如對(duì) Bean 進(jìn)行代理、添加額外的功能等。BeanPostProcessor 在 Bean 實(shí)例化完成后執(zhí)行,用于對(duì) Bean 實(shí)例進(jìn)行后處理。
一言以蔽之,BeanFactoryPostProcessor 主要用于修改 Bean 的定義,而 BeanPostProcessor 主要用于增強(qiáng)或修改 Bean 的實(shí)例。
以上就是BeanFactoryPostProcessor和BeanPostProcessor區(qū)別示例分析的詳細(xì)內(nèi)容,更多關(guān)于BeanFactoryPostProcessor BeanPostProcessor區(qū)別的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java實(shí)現(xiàn)簡(jiǎn)單的掃雷小程序
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)簡(jiǎn)單的掃雷小程序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-04-04Springboot接收POST請(qǐng)求,數(shù)據(jù)為json類型問題
在使用Spring框架中,當(dāng)處理POST請(qǐng)求且內(nèi)容為JSON類型時(shí),應(yīng)使用@RequestBody注解而非@RequestParam,通過@RequestBody可以將JSON數(shù)據(jù)綁定到一個(gè)Map對(duì)象中,然后通過Map的get方法來獲取需要的參數(shù)2022-10-10Java設(shè)計(jì)模式之單例設(shè)計(jì)模式解析
這篇文章主要介紹了Java設(shè)計(jì)模式之單例設(shè)計(jì)模式解析,設(shè)計(jì)模式是在大量的實(shí)踐中總結(jié)和理論化之后優(yōu)選的代碼結(jié)構(gòu)、編程風(fēng)格、以及解決問題的思考方式,設(shè)計(jì)模式免去我們自己再思考和摸索,需要的朋友可以參考下2023-11-11JavaCV實(shí)現(xiàn)多個(gè)MP4視頻的合并
這篇文章主要為大家詳細(xì)介紹了如何使用javacv和ffmpeg框架實(shí)現(xiàn)簡(jiǎn)單快速的合并mp4文件的視頻和音頻,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-10-10解決引入spring-cloud-starter-openfeign后部分類找不到的問題
這篇文章主要介紹了解決引入spring-cloud-starter-openfeign后部分類找不到的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03一文搞懂JMeter engine中HashTree的配置問題
本文主要介紹了JMeter engine中HashTree的配置,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09Spring Boot使用RestTemplate消費(fèi)REST服務(wù)的幾個(gè)問題記錄
這篇文章主要介紹了Spring Boot使用RestTemplate消費(fèi)REST服務(wù)的幾個(gè)問題記錄,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-06-06