Spring中有2個非常重要的接口:BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor,這2個接口面試中也會經常問到,本文我們一起來拿下他們倆。
先來看幾個問題
BeanFactoryPostProcessor是做什麼的?
BeanDefinitionRegistryPostProcessor是幹什麼的?
BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor有什麼區別?
這幾個接口的執行順序是什麼樣的?
Spring容器中主要的4個階段
階段1:Bean註冊階段,此階段會完成所有bean的註冊
階段2:BeanFactory後置處理階段
階段3:註冊BeanPostProcessor
階段4:bean創建階段,此階段完成所有單例bean的註冊和裝載操作,這個階段不是我們本文關注的重點,有興趣的,可以去看之前的文章中有詳細介紹:Bean生命週期詳解
本文介紹的2個接口主要和前2個階段有關係,下面我們主要來看前2個階段。
階段1:Bean註冊階段
概述
spring中所有bean的註冊都會在此階段完成,按照規範,所有bean的註冊必須在此階段進行,其他階段不要再進行bean的註冊。
這個階段spring爲我們提供1個接口:BeanDefinitionRegistryPostProcessor,spring容器在這個階段中會獲取容器中所有類型爲BeanDefinitionRegistryPostProcessor
的bean,然後會調用他們的postProcessBeanDefinitionRegistry
方法,源碼如下,方法參數類型是BeanDefinitionRegistry
,這個類型大家都比較熟悉,即bean定義註冊器,內部提供了一些方法可以用來向容器中註冊bean。
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}
這個接口還繼承了BeanFactoryPostProcessor
接口,這個大家先不用關心,一會階段2中會介紹。
當容器中有多個BeanDefinitionRegistryPostProcessor
的時候,可以通過下面任意一種方式來指定順序
實現
org.springframework.core.PriorityOrdered
接口實現
org.springframework.core.Ordered
接口
執行順序:
PriorityOrdered.getOrder() asc,Ordered.getOrder() asc
下面通過案例來感受一下效果。
案例1:簡單實用
此案例演示BeanDefinitionRegistryPostProcessor
的簡單使用
自定義一個BeanDefinitionRegistryPostProcessor
下面我們定義了一個類,需要實現BeanDefinitionRegistryPostProcessor
接口,然後會讓我們實現2個方法,大家重點關注postProcessBeanDefinitionRegistry
這個方法,另外一個方法來自於BeanFactoryPostProcessor
,一會我們後面在介紹這個方法,在postProcessBeanDefinitionRegistry
方法中,我們定義了一個bean,然後通過registry
將其註冊到容器了,代碼很簡單
package com.javacode2018.lesson003.demo3.test0;
@Component
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
//定義一個字符串類型的bean
AbstractBeanDefinition userNameBdf = BeanDefinitionBuilder.
genericBeanDefinition(String.class).
addConstructorArgValue("路人").
getBeanDefinition();
//將userNameBdf註冊到spring容器中
registry.registerBeanDefinition("userName", userNameBdf);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
同包中來個配置類
package com.javacode2018.lesson003.demo3.test0;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class MainConfig0 {
}
測試用例
package com.javacode2018.lesson003.demo3;
import com.javacode2018.lesson003.demo3.test0.MainConfig0;
import com.javacode2018.lesson003.demo3.test1.MainConfig1;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class BeanDefinitionRegistryPostProcessorTest {
@Test
public void test0() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(MainConfig0.class);
context.refresh();
System.out.println(context.getBean("userName"));
}
}
運行輸出
路人
案例2:多個指定順序
下面我們定義2個BeanDefinitionRegistryPostProcessor
,都實現Ordered
接口,第一個order的值爲2,第二個order的值爲1,我們來看一下具體執行的順序。
第一個
package com.javacode2018.lesson003.demo3.test1;
@Component
public class BeanDefinitionRegistryPostProcessor1 implements BeanDefinitionRegistryPostProcessor, Ordered {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
System.out.println(String.format("BeanDefinitionRegistryPostProcessor1{order=%d},註冊name bean,", this.getOrder()));
//定義一個bean
AbstractBeanDefinition nameBdf = BeanDefinitionBuilder.
genericBeanDefinition(String.class).
addConstructorArgValue("路人甲java").
getBeanDefinition();
//將定義的bean註冊到容器
registry.registerBeanDefinition("name", nameBdf);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
@Override
public int getOrder() {
return 2;
}
}
第二個
package com.javacode2018.lesson003.demo3.test1;
@Component
public class BeanDefinitionRegistryPostProcessor2 implements BeanDefinitionRegistryPostProcessor, Ordered {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
System.out.println(String.format("BeanDefinitionRegistryPostProcessor2{order=%d},註冊car bean,", this.getOrder()));
//定義一個bean
AbstractBeanDefinition nameBdf = BeanDefinitionBuilder.
genericBeanDefinition(String.class).
addConstructorArgValue("保時捷").
getBeanDefinition();
//將定義的bean註冊到容器
registry.registerBeanDefinition("car", nameBdf);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
@Override
public int getOrder() {
return 1;
}
}
上面2個類中的postProcessBeanDefinitionRegistry方法第一行都有輸出,一個可以通過運行結果看到執行的順序。
同包中添加配置類
package com.javacode2018.lesson003.demo3.test1;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class MainConfig1 {
}
測試案例
@Test
public void test1() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(MainConfig1.class);
context.refresh();
context.getBeansOfType(String.class).forEach((beanName, bean) -> {
System.out.println(String.format("%s->%s", beanName, bean));
});
}
運行輸出
BeanDefinitionRegistryPostProcessor2{order=1},註冊car bean,
BeanDefinitionRegistryPostProcessor1{order=2},註冊name bean,
car->保時捷
name->路人甲java
小結
BeanDefinitionRegistryPostProcessor
有個非常重要的實現類:
org.springframework.context.annotation.ConfigurationClassPostProcessor
這個類可能有些人不熟悉,下面這些註解大家應該比較熟悉吧,這些註解都是在上面這個類中實現的,通過這些註解來實現bean的批量註冊
@Configuration
@ComponentScan
@Import
@ImportResource
@PropertySource
有興趣的朋友可以去看一下ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
研究一下上面這些註解的解析過程,可以學到很多東西。
階段2:BeanFactory後置處理階段
概述
到這個階段的時候,spring容器已經完成了所有bean的註冊,這個階段中你可以對BeanFactory中的一些信息進行修改,比如修改階段1中一些bean的定義信息,修改BeanFactory的一些配置等等,此階段spring也提供了一個接口來進行擴展:BeanFactoryPostProcessor
,簡稱bfpp
,接口中有個方法postProcessBeanFactory
,spring會獲取容器中所有BeanFactoryPostProcessor類型的bean,然後調用他們的postProcessBeanFactory
,來看一下這個接口的源碼:
@FunctionalInterface
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
當容器中有多個BeanFactoryPostProcessor
的時候,可以通過下面任意一種方式來指定順序
實現
org.springframework.core.PriorityOrdered
接口實現
org.springframework.core.Ordered
接口
執行順序:
PriorityOrdered.getOrder() asc,Ordered.getOrder() asc
下面通過案例來感受一下效果。
案例
這個案例中演示,在BeanFactoryPostProcessor來修改bean中已經註冊的bean定義的信息,給一個bean屬性設置一個值。
先來定義一個bean
package com.javacode2018.lesson003.demo3.test2;
import org.springframework.stereotype.Component;
@Component
public class LessonModel {
//課程名稱
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "LessonModel{" +
"name='" + name + '\'' +
'}';
}
}
上面這個bean有個name字段,並沒有設置值,下面我們在BeanFactoryPostProcessor來對其設置值。
自定義的BeanFactoryPostProcessor
下面代碼中,我們先獲取lessonModel
這個bean的定義信息,然後給其name
屬性設置了一個值。
package com.javacode2018.lesson003.demo3.test2;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("準備修改lessonModel bean定義信息!");
BeanDefinition beanDefinition = beanFactory.getBeanDefinition("lessonModel");
beanDefinition.getPropertyValues().add("name", "spring高手系列!");
}
}
測試用例
@Test
public void test2() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(MainConfig2.class);
context.refresh();
System.out.println(context.getBean(LessonModel.class));
}
運行輸出
準備修改lessonModel bean定義信息!
LessonModel{name='spring高手系列!'}
結果中可以看出,通過BeanFactoryPostProcessor
修改了容器中已經註冊的bean定義信息。
這個接口的幾個重要實現類
PropertySourcesPlaceholderConfigurer
這個接口做什麼的,大家知道麼?來看一段代碼
<bean class="xxxxx">
<property name="userName" value="${userName}"/>
<property name="address" value="${address}"/>
</bean>
這個大家比較熟悉吧,spring就是在PropertySourcesPlaceholderConfigurer#postProcessBeanFactory
中來處理xml中屬性中的${xxx}
,會對這種格式的進行解析處理爲真正的值。
CustomScopeConfigurer
向容器中註冊自定義的Scope對象,即註冊自定義的作用域實現類,關於自定義的作用域,不瞭解的朋友,建議先看一下:Spring系列第6篇:玩轉bean scope,避免跳坑裏!
這個用法比較簡單,定義一個CustomScopeConfigurer
的bean就可以了,然後通過這個類來註冊自定義的bean。
EventListenerMethodProcessor
處理@EventListener
註解的,即spring中事件機制,需要了解spring事件的:spring事件機制詳解
還有一些實現類,這裏就不介紹了。
使用注意
BeanFactoryPostProcessor
接口的使用有一個需要注意的地方,在其postProcessBeanFactory
方法中,強烈禁止去通過容器獲取其他bean,此時會導致bean的提前初始化,會出現一些意想不到的問題,因爲這個階段中BeanPostProcessor
還未準備好,本文開頭4個階段中有介紹,BeanPostProcessor
是在第3個階段中註冊到spring容器的,而BeanPostProcessor
可以對bean的創建過程進行干預,比如spring中的aop就是在BeanPostProcessor
的一些子類中實現的,@Autowired
也是在BeanPostProcessor
的子類中處理的,此時如果去獲取bean,此時bean不會被BeanPostProcessor
處理,所以創建的bean可能是有問題的,還是通過一個案例給大家演示一下把,通透一些。
來一個簡單的類
package com.javacode2018.lesson003.demo3.test3;
import org.springframework.beans.factory.annotation.Autowired;
public class UserModel {
@Autowired
private String name; //@1
@Override
public String toString() {
return "UserModel{" +
"name='" + name + '\'' +
'}';
}
}
@1:使用了@Autowired,會指定注入
來個配置類
配置類中定義了2個UserModel類型的bean:user1、user2
並且定義了一個String類型的bean:name,這個會注入到UserModel中的name屬性中去。
package com.javacode2018.lesson003.demo3.test3;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class MainConfig3 {
@Bean
public UserModel user1() {
return new UserModel();
}
@Bean
public UserModel user2() {
return new UserModel();
}
@Bean
public String name() {
return "路人甲Java,帶大家成功java高手!";
}
}
測試用例
輸出容器中所有UserModel類型的bean
@Test
public void test3() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(MainConfig3.class);
context.refresh();
context.getBeansOfType(UserModel.class).forEach((beanName, bean) -> {
System.out.println(String.format("%s->%s", beanName, bean));
});
}
運行輸出
user1->UserModel{name='路人甲Java,帶大家成功java高手!'}
user2->UserModel{name='路人甲Java,帶大家成功java高手!'}
效果不用多解釋,大家一看就懂,下面來重點。
添加一個BeanFactoryPostProcessor
在postProcessBeanFactory
方法中去獲取一下user1這個bean
package com.javacode2018.lesson003.demo3.test3;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
beanFactory.getBean("user1");
}
}
再次運行輸出
user1->UserModel{name='null'}
user2->UserModel{name='路人甲Java,帶大家成功java高手!'}
注意,user1中的name變成null了,什麼情況?
是因爲@Autowired註解是在AutowiredAnnotationBeanPostProcessor
中解析的,spring容器調用BeanFactoryPostProcessor#postProcessBeanFactory
的使用,此時spring容器中還沒有AutowiredAnnotationBeanPostProcessor
,所以此時去獲取user1這個bean的時候,@Autowired並不會被處理,所以name是null。
源碼
4個階段的源碼
4個階段的源碼爲位於下面這個方法中
org.springframework.context.support.AbstractApplicationContext#refresh
這個方法中截取部分代碼如下:
// 對應階段1和階段2:調用上下文中註冊爲bean的工廠處理器,即調用本文介紹的2個接口中的方法
invokeBeanFactoryPostProcessors(beanFactory);
// 對應階段3:註冊攔截bean創建的bean處理器,即註冊BeanPostProcessor
registerBeanPostProcessors(beanFactory);
// 對應階段3:實例化所有剩餘的(非延遲初始化)單例。
finishBeanFactoryInitialization(beanFactory);
階段1和階段2的源碼位於下面方法中,代碼比較簡單,強烈建議大家去看一下,幾分鐘就可以看懂了。
org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(org.springframework.beans.factory.config.ConfigurableListableBeanFactory, java.util.List<org.springframework.beans.factory.config.BeanFactoryPostProcessor>)
總結
注意spring的4個階段:bean定義階段、BeanFactory後置處理階段、BeanPostProcessor註冊階段、單例bean創建組裝階段
BeanDefinitionRegistryPostProcessor會在第一個階段被調用,用來實現bean的註冊操作,這個階段會完成所有bean的註冊
BeanFactoryPostProcessor會在第2個階段被調用,到這個階段時候,bean此時已經完成了所有bean的註冊操作,這個階段中你可以對BeanFactory中的一些信息進行修改,比如修改階段1中一些bean的定義信息,修改BeanFactory的一些配置等等
階段2的時候,2個禁止操作:禁止註冊bean、禁止從容器中獲取bean
本文介紹的2個接口的實現類可以通過
PriorityOrdered
接口或者Ordered
接口來指定順序
案例源碼
https://gitee.com/javacode2018/spring-series
路人甲java所有案例代碼以後都會放到這個上面,大家watch一下,可以持續關注動態。
課後作業
2個問題,歡迎留言討論,會有紅包驚喜哦!
問題1
大家去讀一下org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors的源碼,將內部主要的執行過程通過文字描述出來,然後留言
問題2
BeanPostProcessor和BeanFactoryPostProcessors有何區別?留言
Spring系列
更多好文章
世界上最好的關係是相互成就,點贊轉發 感恩開心????
路人甲java
▲長按圖片識別二維碼關注
路人甲Java:工作10年的前阿里P7,所有文章以系列的方式呈現,帶領大家成爲java高手,目前已出:java高併發系列、mysql高手系列、Maven高手系列、mybatis系列、spring系列,正在連載springcloud系列,歡迎關注!