Spring注解驅(qū)動(dòng)之BeanPostProcessor后置處理器講解
概述
在學(xué)習(xí)Spring的時(shí)候,在了解基本用法的時(shí)候,如果有時(shí)間一定要深入源碼了解Spring的底層原理,這樣在做一些適配工作、寫(xiě)一些輪子的時(shí)候就會(huì)比較容易,否則會(huì)很難,甚至一頭霧水,無(wú)法完成工作。
吃透Spring的原理和源碼,往往可以拉開(kāi)人們之間的差距,當(dāng)前只要是使用Java技術(shù)棧開(kāi)發(fā)的Web項(xiàng)目,幾乎都會(huì)使用Spring框架。
而且目前各招聘網(wǎng)站上對(duì)于Java開(kāi)發(fā)的要求幾乎清一色的都是熟悉或者精通Spring,所以,你很有必要學(xué)習(xí)Spring的細(xì)節(jié)知識(shí)點(diǎn)。
BeanPostProcessor后置處理器概述
首先,看下BeanPostProcessor的源碼。
package org.springframework.beans.factory.config; import org.springframework.beans.BeansException; public interface BeanPostProcessor { Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException; Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException; }
從源碼可以看出,BeanPostProcessor是一個(gè)接口,其中有兩個(gè)方法:
postProcessBeforeInitialization和postProcessAfterInitialization這兩個(gè)方法分別是在Spring容器中的bean初始化前后執(zhí)行,所以Spring容器中的每個(gè)bean對(duì)象初始化前后,都會(huì)執(zhí)行BeanPostProcessor接口的兩個(gè)方法。
也就是說(shuō),postProcessBeforeInitialization方法會(huì)在bean實(shí)例化和屬性設(shè)置之后,自定義初始化方法之前被調(diào)用,而postProcessAfterInitialization方法會(huì)在自定義初始化方法之后被調(diào)用。當(dāng)容器中存在多個(gè)BeanPostProcessor的實(shí)現(xiàn)類(lèi)時(shí),會(huì)按照它們?cè)谌萜髦凶?cè)的順序執(zhí)行。對(duì)于自定義的BeanPostProcessor實(shí)現(xiàn)類(lèi),還可以讓其實(shí)現(xiàn)Ordered接口自定義排序。
因此我們可以在每個(gè)bean對(duì)象初始化前后,加上自己的邏輯。實(shí)現(xiàn)方式是自定義一個(gè)BeanPostProcessor接口的實(shí)現(xiàn)類(lèi),例如MyBeanPostProcessor,然后在該類(lèi)的postProcessBeforeInitialization和postProcessAfterInitialization這兩個(gè)方法寫(xiě)上自定義邏輯。
BeanPostProcessor后置處理器實(shí)例
我們創(chuàng)建一個(gè)MyBeanPostProcessor類(lèi),實(shí)現(xiàn)BeanPostProcessor接口,如下所示。
package com.meimeixia.bean; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.stereotype.Component; @Component // 將后置處理器加入到容器中,這樣的話,Spring就能讓它工作了,否則無(wú)法工作 public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("postProcessBeforeInitialization..." + beanName + "=>" + bean); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("postProcessAfterInitialization..." + beanName + "=>" + bean); return bean; } }
接下來(lái),我們應(yīng)該是要編寫(xiě)測(cè)試用例來(lái)進(jìn)行測(cè)試了。
package com.meimeixia.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import com.meimeixia.bean.Car; @ComponentScan("com.meimeixia.bean") @Configuration public class MainConfigOfLifeCycle { @Bean(initMethod="init", destroyMethod="destroy") public Car car() { return new Car(); } }
第二處改動(dòng)是將Cat類(lèi)上添加的@Scope(“prototype”)注解給注釋掉,因?yàn)樵蹅冎白鰷y(cè)試的時(shí)候,也是將Cat對(duì)象設(shè)置成多實(shí)例bean了。
package com.meimeixia.bean; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; // @Scope("prototype") @Component public class Cat implements InitializingBean, DisposableBean { public Cat() { System.out.println("cat constructor..."); } /** * 會(huì)在容器關(guān)閉的時(shí)候進(jìn)行調(diào)用 */ @Override public void destroy() throws Exception { // TODO Auto-generated method stub System.out.println("cat destroy..."); } /** * 會(huì)在bean創(chuàng)建完成,并且屬性都賦好值以后進(jìn)行調(diào)用 */ @Override public void afterPropertiesSet() throws Exception { // TODO Auto-generated method stub System.out.println("cat afterPropertiesSet..."); } }
好了,現(xiàn)在咱們就可以編寫(xiě)測(cè)試用例來(lái)進(jìn)行測(cè)試了。
可喜的是,我們也不用再編寫(xiě)一個(gè)測(cè)試用例了,直接運(yùn)行IOCTest_LifeCycle類(lèi)中的test01()方法就行,該方法的代碼如下所示。
@Test public void test01() { // 1. 創(chuàng)建IOC容器 AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class); System.out.println("容器創(chuàng)建完成"); // 關(guān)閉容器 applicationContext.close(); }
此時(shí),運(yùn)行IOCTest_LifeCycle類(lèi)中的test01()方法,輸出的結(jié)果信息如下所示。
可以看到,postProcessBeforeInitialization方法會(huì)在bean實(shí)例化和屬性設(shè)置之后,自定義初始化方法之前被調(diào)用,而postProcessAfterInitialization方法會(huì)在自定義初始化方法之后被調(diào)用。
當(dāng)然了,也可以讓我們自己寫(xiě)的MyBeanPostProcessor類(lèi)來(lái)實(shí)現(xiàn)Ordered接口自定義排序,如下所示。
package com.meimeixia.bean; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; /** * 后置處理器,在初始化前后進(jìn)行處理工作 * @author liayun * */ @Component // 將后置處理器加入到容器中,這樣的話,Spring就能讓它工作了 public class MyBeanPostProcessor implements BeanPostProcessor, Ordered { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("postProcessBeforeInitialization..." + beanName + "=>" + bean); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("postProcessAfterInitialization..." + beanName + "=>" + bean); return bean; } @Override public int getOrder() { return 3; } }
BeanPostProcessor后置處理器作用
后置處理器可用于bean對(duì)象初始化前后進(jìn)行邏輯增強(qiáng)。
Spring提供了BeanPostProcessor接口的很多實(shí)現(xiàn)類(lèi),例如AutowiredAnnotationBeanPostProcessor用于@Autowired注解的實(shí)現(xiàn),AnnotationAwareAspectJAutoProxyCreator用于Spring AOP的動(dòng)態(tài)代理等等。
除此之外,我們還可以自定義BeanPostProcessor接口的實(shí)現(xiàn)類(lèi),在其中寫(xiě)入咱們需要的邏輯。
bean的初始化和銷(xiāo)毀流程
我們知道BeanPostProcessor的postProcessBeforeInitialization()方法是在bean的初始化之前被調(diào)用;而postProcessAfterInitialization()方法是在bean初始化的之后被調(diào)用。
并且bean的初始化和銷(xiāo)毀方法我們可以通過(guò)如下方式進(jìn)行指定。
1. 通過(guò)@Bean指定init-method和destroy-method
@Bean(initMethod="init", destroyMethod="destroy")
2. 通過(guò)讓bean實(shí)現(xiàn)InitializingBean和DisposableBean這倆接口
@Componentpublic class Cat implements InitializingBean, DisposableBean {}
3. 使用JSR-250規(guī)范里面定義的@PostConstruct和@PreDestroy這倆注解
@PostConstruct
:在bean創(chuàng)建完成并且屬性賦值完成之后,來(lái)執(zhí)行初始化方法@PreDestroy
:在容器銷(xiāo)毀bean之前通知我們進(jìn)行清理工作
4. 通過(guò)讓bean實(shí)現(xiàn)BeanPostProcessor接口
@Component // 將后置處理器加入到容器中,這樣的話,Spring就能讓它工作了 public class MyBeanPostProcessor implements BeanPostProcessor, Ordered {}
通過(guò)以上四種方式就可以對(duì)bean的整個(gè)生命周期進(jìn)行控制:
- bean的實(shí)例化:調(diào)用bean的構(gòu)造方法,我們可以在bean的無(wú)參構(gòu)造方法中執(zhí)行相應(yīng)的邏輯。
- bean的初始化:在初始化時(shí)可以通過(guò)BeanPostProcessor的postProcessBeforeInitialization()方法和postProcessAfterInitialization()方法進(jìn)行攔截,執(zhí)行自定義的邏輯。通過(guò)@PostConstruct注解、InitializingBean和init-method來(lái)指定bean初始化前后執(zhí)行的方法,在該方法中可以執(zhí)行自定義的邏輯。
- bean的銷(xiāo)毀:可以通過(guò)@PreDestroy注解、DisposableBean和destroy-method來(lái)指定bean在銷(xiāo)毀前執(zhí)行的方法,在方法中可以執(zhí)行自定義的邏輯。
所以,通過(guò)上述4種方式,我們可以控制Spring中bean的整個(gè)生命周期。
BeanPostProcessor源碼解析
如果想深刻理解BeanPostProcessor的工作原理,那么就不得不看下相關(guān)的源碼,我們可以在MyBeanPostProcessor類(lèi)的postProcessBeforeInitialization()方法和postProcessAfterInitialization()方法這兩處打上斷點(diǎn)來(lái)進(jìn)行調(diào)試,如下所示。
public class MyBeanPostProcessor implements BeanPostProcessor, Ordered { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("postProcessBeforeInitialization" + ",beanName:"+beanName + ",bean=>" + bean); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("postProcessAfterInitialization" + ",beanName:"+beanName + ",bean=>" + bean); return bean; } @Override public int getOrder() { return 3; } }
通過(guò)斷點(diǎn)調(diào)試,我們可以看到,在applyBeanPostProcessorBeforeInitialization()方法中,會(huì)遍歷所有BeanPostProcessor對(duì)象,然后依次執(zhí)行所有BeanPostProcessor對(duì)象的postProcessorBeforeInitialization()方法,一旦BeanPostProcessor對(duì)象的postProcessBeforeInitialization()方法返回null以后,則后面的BeanPostProcessor對(duì)象便不再執(zhí)行了,而是直接退出for循環(huán)。這些都是我們看源碼看到的。
看Spring源碼,我們還看到一個(gè)細(xì)節(jié),在Spring中調(diào)用initializeBean()方法之前,還調(diào)用了populateBean()方法來(lái)為bean的屬性賦值。
經(jīng)過(guò)一系列的跟蹤源碼分析,我們可以將關(guān)鍵代碼的調(diào)用過(guò)程使用如下偽代碼表述出來(lái)。
populateBean(beanName, mbd, instanceWrapper); // 給bean進(jìn)行屬性賦值 initializeBean(beanName, exposedObject, mbd) { applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); invokeInitMethods(beanName, wrappedBean, mbd); // 執(zhí)行自定義初始化 applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); }
也就是說(shuō),在Spring中,調(diào)用initializeBean()方法之前,調(diào)用了populateBean()方法為bean的屬性賦值,為bean的屬性賦好值之后,再調(diào)用initializeBean()方法進(jìn)行初始化。
在initializeBean()中,調(diào)用自定義的初始化方法(即invokeInitMethods())之前,調(diào)用了applyBeanPostProcessorsBeforeInitialization()方法,而在調(diào)用自定義的初始化方法之后,又調(diào)用了applyBeanPostProcessorsAfterInitialization()方法。至此,整個(gè)bean的初始化過(guò)程就這樣結(jié)束了。
BeanPostProcessor接口在Spring底層的應(yīng)用案例
ApplicationContextAwareProcessor類(lèi)
org.springframework.context.support.ApplicationContextAwareProcessor是BeanPostProcessor接口的一個(gè)實(shí)現(xiàn)類(lèi),這個(gè)類(lèi)的作用是可以向組件中注入IOC容器,大致的源碼如下。
注意:我這里的Spring版本為4.3.12.RELEASE。
那具體如何使用ApplicationContextAwareProcessor類(lèi)向組件中注入IOC容器呢?
如果需要向組件中注入IOC容器,那么可以讓組件實(shí)現(xiàn)ApplicationContextAware接口。
例如,我們創(chuàng)建一個(gè)創(chuàng)建一個(gè)Dog類(lèi),使其實(shí)現(xiàn)ApplicationContextAware接口,此時(shí),我們需要實(shí)現(xiàn)ApplicationContextAware接口中的setApplicationContext()方法,在setApplicationContext()方法中有一個(gè)ApplicationContext類(lèi)型的參數(shù),這個(gè)就是IOC容器對(duì)象,我們可以在Dog類(lèi)中定義一個(gè)ApplicationContext類(lèi)型的成員變量,然后在setApplicationContext()方法中為這個(gè)成員變量賦值,此時(shí)就可以在Dog類(lèi)中的
其他方法中使用ApplicationContext對(duì)象了,如下所示。
package com.meimeixia.bean; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; /** * ApplicationContextAwareProcessor這個(gè)類(lèi)的作用是可以幫我們?cè)诮M件里面注入IOC容器, * 怎么注入呢?我們想要IOC容器的話,比如我們這個(gè)Dog組件,只需要實(shí)現(xiàn)ApplicationContextAware接口就行 * */ @Component public class Dog implements ApplicationContextAware { private ApplicationContext applicationContext; public Dog() { System.out.println("dog constructor..."); } // 在對(duì)象創(chuàng)建完成并且屬性賦值完成之后調(diào)用 @PostConstruct public void init() { System.out.println("dog...@PostConstruct..."); } // 在容器銷(xiāo)毀(移除)對(duì)象之前調(diào)用 @PreDestroy public void destory() { System.out.println("dog...@PreDestroy..."); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { // 在這兒打個(gè)斷點(diǎn)調(diào)試一下 // TODO Auto-generated method stub this.applicationContext = applicationContext; } }
看到這里,相信不少小伙伴們都有一種很熟悉的感覺(jué),沒(méi)錯(cuò),我之前也在項(xiàng)目中使用過(guò)!是的,這就是BeanPostProcessor在Spring底層的一種使用場(chǎng)景。至于上面的案例代碼為何會(huì)在setApplicationContext()方法中獲取到ApplicationContext對(duì)象,這就是ApplicationContextAwareProcessor類(lèi)的功勞了!
接下來(lái),我們就深入分析下ApplicationContextAwareProcessor類(lèi)。
我們先來(lái)看下ApplicationContextAwareProcessor類(lèi)中對(duì)于postProcessBeforeInitialization()方法的實(shí)現(xiàn),如下所示。
在bean初始化之前,首先對(duì)當(dāng)前bean的類(lèi)型進(jìn)行判斷,如果當(dāng)前bean的類(lèi)型不是EnvironmentAware,不是EmbeddedValueResolverAware,不是ResourceLoaderAware,不是ApplicationEventPublisherAware,不是MessageSourceAware,也不是ApplicationContextAware,那么直接返回bean。
如果是上面類(lèi)型中的一種類(lèi)型,那么最終會(huì)調(diào)用invokeAwareInterfaces()方法,并將bean傳遞給該方法。
invokeAwareInterfaces()方法又是個(gè)什么呢?我們繼續(xù)看invokeAwareInterfaces()方法的源碼,如下所示。
可以看到invokeAwareInterfaces()方法的源碼比較簡(jiǎn)單,就是判斷當(dāng)前bean屬于哪種接口類(lèi)型,然后將bean強(qiáng)轉(zhuǎn)為哪種接口類(lèi)型的對(duì)象,接著調(diào)用接口中的方法,將相應(yīng)的參數(shù)傳遞到接口的方法中。
我們可以看到,此時(shí)會(huì)將this.applicationContext傳遞到ApplicationContextAware接口的setApplicationContext()方法中。所以,我們?cè)贒og類(lèi)的setApplicationContext()方法中就可以直接接收到ApplicationContext對(duì)象了。
BeanValidationPostProcessor類(lèi)
org.springframework.validation.beanvalidation.BeanValidationPostProcessor類(lèi)注意是用來(lái)為bean進(jìn)行校驗(yàn)操作的,當(dāng)我們創(chuàng)建bean,并為bean賦值后,我們可以通過(guò)BeanValidationPostProcessor類(lèi)為bean進(jìn)行校驗(yàn)操作。BeanValidationPostProcessor類(lèi)源碼如下:
這里,我們也來(lái)看看postProcessBeforeInitialization()方法和postProcessAfterInitialization()方法的實(shí)現(xiàn),如下所示。
InitDestroyAnnotationBeanPostProcessor類(lèi)
org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor類(lèi)主要用來(lái)處理@PostConstruct注解和@PreDestroy注解。
使用了@PostConstruct注解和@PreDestroy注解來(lái)標(biāo)注方法,Spring怎么就知道什么時(shí)候執(zhí)行@PostConstruct注解標(biāo)注的方法,什么時(shí)候執(zhí)行@PreDestroy注解標(biāo)注的方法呢?這就要?dú)w功于InitDestroyAnnotationBeanPostProcessor類(lèi)了。
接下來(lái),我們也通過(guò)Debug的方式來(lái)跟進(jìn)下代碼的執(zhí)行流程。首先,在Dog類(lèi)的initt()方法上打上一個(gè)斷點(diǎn),如下所示。
在InitDestroyAnnotationBeanPostProcessor類(lèi)的postProcessBeforeInitialization()方法中,首先會(huì)找到bean中有關(guān)生命周期的注解,比如@PostConstruct注解等,找到這些注解之后,則將這些信息賦值給LifecycleMetadata類(lèi)型的變量metadata,之后調(diào)用metadata的invokeInitMethods()方法,通過(guò)反射來(lái)調(diào)用標(biāo)注了@PostConstruct注解的方法。
這就是為什么標(biāo)注了@PostConstruct注解的方法會(huì)被Spring執(zhí)行的原因。
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass()); try { metadata.invokeInitMethods(bean, beanName); } catch (InvocationTargetException ex) { throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException()); } catch (Throwable ex) { throw new BeanCreationException(beanName, "Failed to invoke init method", ex); } return bean; }
AutowiredAnnotationBeanPostProcessor類(lèi)
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor類(lèi)主要是用于處理標(biāo)注了@Autowired注解的變量或方法。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
IntelliJ IDEA打開(kāi)多個(gè)Maven的module且相互調(diào)用代碼的方法
這篇文章主要介紹了IntelliJ IDEA打開(kāi)多個(gè)Maven的module且相互調(diào)用代碼的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-02-02Java工程使用ffmpeg進(jìn)行音視頻格式轉(zhuǎn)換的實(shí)現(xiàn)
FFmpeg是一套可以用來(lái)記錄、轉(zhuǎn)換數(shù)字音頻、視頻,并能將其轉(zhuǎn)化為流的開(kāi)源計(jì)算機(jī)程序,本文主要介紹了Java工程使用ffmpeg進(jìn)行音視頻格式轉(zhuǎn)換的實(shí)現(xiàn)2024-02-02intelliJ IDEA 多行選中相同內(nèi)容的快捷鍵分享
這篇文章主要介紹了intelliJ IDEA 多行選中相同內(nèi)容的快捷鍵分享,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02