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