SpringBoot擴展點之BeanPostProcessor
前言
Springboot(Spring)的擴展點其實有很多,但是都有一個共同點,都是圍繞著Bean和BeanFactory(容器)展開的,其實這也很好理解,Spring的核心是控制反轉(zhuǎn)、依賴注入、面向切面編程,再拋開所有的枝枝節(jié)節(jié),你發(fā)現(xiàn)了什么?Spring提供了一個容器,來管理Bean,整個生態(tài)好像是都圍繞這個展開。研究源碼意義,一方面是在于技術(shù)本身,另一方面也在于理解接受其中的思想。
沒有目的的亂走總是會迷路,有了目標就不一樣了,所以這篇文章是圍繞以下幾個問題展開的,這也是我想和大家分享的內(nèi)容:(如果你和我的疑問一樣,關(guān)注,收藏+點贊,不迷路哦)
1、BeanPostProcessor接口的功能特性是什么樣的?
2、BeanPostProcessor接口怎么實現(xiàn)擴展?
3、BeanPostProcessor接口的實現(xiàn)類的工作原理是什么?
4、BeanPostProcessor接口的應(yīng)用場景有哪些?
功能特性
1、BeanPostProcessor是Bean級別的擴展接口,在Spring管理的Bean實例化完成后,預(yù)留了兩種擴展點;
2、這兩處擴展的實現(xiàn)方式就是實現(xiàn)BeanPostProcessor接口,并將實現(xiàn)類注冊到Spring容器中;
3、兩種擴展點分別是BeanPostProcessor接口的postProcessBeforeInitialization方法和postProcessAfterInitialization方法;
4、postProcessBeforeInitialization方法的執(zhí)行時機是在Spring管理的Bean實例化、屬性注入完成后,InitializingBean#afterPropertiesSet方法以及自定義的初始化方法之前;
5、postProcessAfterInitialization方法的執(zhí)行時機是在InitializingBean#afterPropertiesSet方法以及自定義的初始化方法之后;
6、BeanPostProcessor接口的實現(xiàn)類的postProcessBeforeInitialization方法和postProcessAfterInitialization方法,在在Spring管理的每個bean初始化后都會執(zhí)行到;
實現(xiàn)方式
1、定義一個實體類Dog,并實現(xiàn)InitializingBean接口,并且實現(xiàn)afterPropertiesSet()。其中afterPropertiesSet()和init()是為了演示BeanPostProcessor接口的實現(xiàn)類的postProcessBeforeInitialization方法和postProcessAfterInitialization方法的執(zhí)行時機;
@Getter
@Setter
@Slf4j
public class Dog implements InitializingBean {
private String name = "旺財";
private String color = "黑色";
public Dog() {
log.info("---dog的無參構(gòu)造方法被執(zhí)行");
}
@Override
public void afterPropertiesSet() throws Exception {
log.info("---afterPropertiesSet被執(zhí)行");
}
public void init() {
log.info("---initMethod被執(zhí)行");
}
}
把Dog類注冊到Spring容器中,并設(shè)置了Bean實例化后的初始化方法;
@Configuration
public class SpringConfig {
@Bean(initMethod = "init")
public Dog dog(){
Dog dog = new Dog();
return dog;
}
}
2、定義MyBeanPostProcessor,并且實現(xiàn)BeanPostProcessor接口;(這里類的命名和方法內(nèi)邏輯僅是為了演示需要,實際開發(fā)中需要以實際邏輯來替換掉演示內(nèi)容)
@Component
@Slf4j
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (beanName.equals("dog")) {
log.info("postProcessBeforeInitialization---" + beanName);
//如果特定的bean實例化完成后,還未執(zhí)行InitializingBean.afterPropertiesSet()方法之前,有一些其他操作,可以在這里實現(xiàn)
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (beanName.equals("dog")) {
log.info("postProcessAfterInitialization---" + beanName);
//如果特定的bean實例化完成,InitializingBean.afterPropertiesSet()方法執(zhí)行后,有一些其他操作,可以在這里實現(xiàn)
}
return bean;
}
}
3、編寫單元測試,來驗證結(jié)果;
@SpringBootTest
@Slf4j
public class FanfuApplicationTests {
@Test
public void test3(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.fanfu");
Dog dog = ((Dog) context.getBean("dog"));
log.info(dog.getName());
}
}
圖片
結(jié)論:從單元測試的執(zhí)行結(jié)果來看,驗證了Spring的擴展點BeanPostProcessor的執(zhí)行時機,即postProcessBeforeInitialization方法的執(zhí)行時機是在Spring管理的Bean實例化、屬性注入完成后,InitializingBean#afterPropertiesSet方法以及自定義的初始化方法之前;postProcessAfterInitialization方法的執(zhí)行時機是在InitializingBean#afterPropertiesSet方法以及自定義的初始化方法之前之后;
以上演示了BeanPostProcessor作為Springboot的擴展點之一的實現(xiàn)方式和執(zhí)行時機,下面從示例入手,來了解一下其基本的工作原理,正所謂知其然還要知其所以然嘛。
工作原理
BeanPostProcessor的工作原理的關(guān)鍵其實就是兩點,第一,BeanPostProcessor的實現(xiàn)類是什么時候被注冊的?第二,BeanPostProcessor的實現(xiàn)類的postProcessBeforeInitialization方法和postProcessAfterInitialization方法是如何被執(zhí)行的?
注冊時機
1、BeanPostProcessor中的兩個擴展方法中,postProcessBeforeInitialization方法是先被執(zhí)行的,即Bean實例化和屬性注入完成之后,通過實現(xiàn)方式示例代碼的Debug,找到了BeanPostProcessor接口的實現(xiàn)類到Spring容器中的入口,即AbstractApplicationContext#refresh--->registerBeanPostProcessors
圖片
2、進入到AbstractApplicationContext#registerBeanPostProcessors方法內(nèi),會發(fā)現(xiàn)這段代碼很干凈,即依賴于PostProcessorRegistrationDelegate類的registerBeanPostProcessors()方法;
protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
}
3、進入到PostProcessorRegistrationDelegate類的registerBeanPostProcessors()方法又是另一番洞天:第一步,獲取所有實現(xiàn)BeanPostProcessor接口的實現(xiàn)類的名稱,實現(xiàn)方式示例中的MyBeanPostProcessors就在其中;
圖片
第二步,提前注冊BeanPostProcessorChecker,主要用途是用于Bean創(chuàng)建過程中的日志信息打印記錄;
圖片
第三步,就是把所有的BeanPostProcessor接口的實現(xiàn)類,按照是否實現(xiàn)PriorityOrdered接口、是否實現(xiàn)Ordered接口、其他,分為三組;
圖片
最后,下面的內(nèi)容很長,不過很簡單,即按第二步分成的三類,依次注冊,具體的順序是實現(xiàn)PriorityOrdered接口BeanPostProcessor接口的實現(xiàn)類、實現(xiàn)實現(xiàn)Ordered接口BeanPostProcessor接口的實現(xiàn)類、其他的BeanPostProcessor接口的實現(xiàn)類;
圖片
總結(jié),BeanPostProcessor的注冊時機是在Spring容器啟動過程中,即BeanFactoryPostProcessor擴展點的邏輯執(zhí)行完成后,緊接著就開始了BeanPostProcessor的注冊,其具體的注冊邏輯在PostProcessorRegistrationDelegate#registerBeanPostProcessors()。
圖片
執(zhí)行時機
從實現(xiàn)方式的示例中驗證得知,BeanPostProcessor接口的實現(xiàn)類的執(zhí)行時機是在Spring管理的Bean實例化、屬性注入完成后,那么找到Dog類的實例化入口,那么離BeanPostProcessor接口的實現(xiàn)類的執(zhí)行時機也就不遠了。
1、通過Debug調(diào)試,注冊到Spring容器中的Dog類的實例化入口,即org.springframework.context.support.AbstractApplicationContext#refresh--->finishBeanFactoryInitialization();
圖片
2、進入到finishBeanFactoryInitialization(),發(fā)現(xiàn)實現(xiàn)方式示例中的Dog類是在DefaultListableBeanFactory#preInstantiateSingletons--->getBean()中實例化完成的。這里大致介紹一下getBean()業(yè)務(wù)邏輯:當獲取某一個bean時,先查詢緩存確定是否存在,若存在,則直接返回,若不存在,則開始創(chuàng)建Bean,若Bean內(nèi)依賴了另外一個Bean,則是上述過程的一個遞歸。
圖片
3、從getBean方法進入后,主要過程是AbstractBeanFactory#doGetBean-->AbstractBeanFactory#createBean-->AbstractAutowireCapableBeanFactory#doCreateBean-->AbstractAutowireCapableBeanFactory#createBeanInstance,至此完成了Bean的實例化和屬性注入。到這要打起精神了,要找的BeanPostProcessor接口的實現(xiàn)類的執(zhí)行時機馬上就到。果然在AbstractAutowireCapableBeanFactory#doCreateBean方法中,Dog類實例化完后,又調(diào)用initializeBean()進行bean的初始化操作,而BeanPostProcessor接口的實現(xiàn)類的postProcessBeforeInitialization方法和postProcessAfterInitialization方法的執(zhí)行時機分別是在Bean的初始化方法執(zhí)行前后觸發(fā),那么這個方法大概率就是BeanPostProcessor接口的實現(xiàn)類的執(zhí)行時機的入口了。
圖片
4、進入到initializeBean()一看,判斷的果然沒錯,先執(zhí)行BeanPostProcessor接口實現(xiàn)類的postProcessBeforeInitialization方法,接著如果bean實現(xiàn)了InitializingBean或者自定義了initMethod,就會在這里執(zhí)行InitializingBean#afterPropertiesSet和initMethod方法,最后會執(zhí)行執(zhí)行BeanPostProcessor接口實現(xiàn)類的postProcessAfterInitialization方法;
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
invokeAwareMethods(beanName, bean);
return null;
}, getAccessControlContext());
}else {
invokeAwareMethods(beanName, bean);
}
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
//執(zhí)行BeanPostProcessor接口實現(xiàn)類的postProcessBeforeInitialization方法
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
//如果bean實現(xiàn)了InitializingBean或者自定義了initMethod,
//會在這里執(zhí)行InitializingBean#afterPropertiesSet和initMethod方法
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
}
if (mbd == null || !mbd.isSynthetic()) {
//執(zhí)行BeanPostProcessor接口實現(xiàn)類的postProcessAfterInitialization方法
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
5、下面分別再進入到applyBeanPostProcessorsBeforeInitialization()、invokeInitMethods()、applyBeanPostProcessorsAfterInitialization(),看看具體是怎么實現(xiàn)的。先來看applyBeanPostProcessorsBeforeInitialization():如果仔細研究過之前的Springboot擴展點之BeanFactoryPostProcessor 、Springboot擴展點之BeanDefinitionRegistryPostProcessor 、Springboot擴展點之ApplicationContextInitializer這幾篇文章,那么對這個方法的套路就再熟悉不過了:先獲取到所有注冊到Spring容器中BeanPostProcessor接口的實現(xiàn)類,然后再遍歷執(zhí)行觸發(fā)方法,就這么樸實無華。
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
for (BeanPostProcessor processor : getBeanPostProcessors()) {
Object current = processor.postProcessBeforeInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
return result;
}
6、再來看一下,AbstractAutowireCapableBeanFactory#invokeInitMethods,邏輯也是很清晰,先判斷是否實現(xiàn)了InitializingBean接口,如果實現(xiàn)了InitializingBean接口,就會觸發(fā)執(zhí)行afterPropertiesSet(),然后判斷有沒有自定義initMethod方法,如果有,則在這里開始執(zhí)行;
protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd)
throws Throwable {
//判斷是否實現(xiàn)了InitializingBean接口
boolean isInitializingBean = (bean instanceof InitializingBean);
if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
if (logger.isTraceEnabled()) {
logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
}
if (System.getSecurityManager() != null) {
try {
AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
((InitializingBean) bean).afterPropertiesSet();
return null;
}, getAccessControlContext());
}
catch (PrivilegedActionException pae) {
throw pae.getException();
}
}else {
//如果實現(xiàn)了InitializingBean接口,就會重寫afterPropertiesSet(),這里就會觸發(fā)執(zhí)行
((InitializingBean) bean).afterPropertiesSet();
}
}
if (mbd != null && bean.getClass() != NullBean.class) {
//判斷有沒有自定義initMethod方法,如果有,則在這里開始執(zhí)行;
String initMethodName = mbd.getInitMethodName();
if (StringUtils.hasLength(initMethodName) &&
!(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
!mbd.isExternallyManagedInitMethod(initMethodName)) {
invokeCustomInitMethod(beanName, bean, mbd);
}
}
}
7、最后來看一下applyBeanPostProcessorsAfterInitialization(),前面applyBeanPostProcessorsBeforeInitialization()看懂了,這里就沒有必要分析了,如出一轍,熟悉配方,熟悉的味道。
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
for (BeanPostProcessor processor : getBeanPostProcessors()) {
Object current = processor.postProcessAfterInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
return result;
}
至此,Springboot擴展點BeanPostProcessor的工作原理分析完了,歸根結(jié)底就是兩點,
第一,在Spring容器初始化的過程中,完成擴展點的注冊;
第二,在Spring中Bean完成實例化和屬性注入后,開始觸發(fā)已注冊的擴展點的擴展動作。
內(nèi)容很長,但是邏輯簡單,希望閱讀到這篇文章的小伙伴能夠有耐心看完,因為我在研究清楚整個過程后,我是感覺獲益良多的,希望你也是。
應(yīng)用場景
其實了解了BeanPostProcessor的功能特性、實現(xiàn)方式和工作原理,在遇到類似的業(yè)務(wù)需求的時候都可以應(yīng)用這個擴展點,這里舉兩個我想到的應(yīng)用場景:
處理自定義注解
在程序中我們可以自定義注解并標到相應(yīng)的類上,當個類注冊到Spring容器中,并實例化完成后,希望觸發(fā)自定義注解對應(yīng)的一些其他操作的時候,就可以通過BeanPostProcessor來實現(xiàn)。
參數(shù)校驗
前面有兩篇文章優(yōu)雅的Springboot參數(shù)校驗(一) 、優(yōu)雅的Springboot參數(shù)校驗(二) 和大家分享了參數(shù)校驗具體實現(xiàn)方式,其核心原理正是用到了BeanPostProcessor擴展點,具體的實現(xiàn)類是org.springframework.validation.beanvalidation.BeanValidationPostProcessor