Spring系列14:IoC容器的擴展點
回顧
知識需要成體系地學習,本系列文章前后有關聯,建議按照順序閱讀,上一篇我們詳細介紹了Spring Bean的生命周期和豐富的擴展點,沒有閱讀的強烈建議先閱讀,本篇來詳細講講容器提供的擴展點,完整的生命周期圖鎮樓,

本文內容
- 詳解BeanPostProcessor
- 詳解
BeanFactoryPostProcessor - 詳解
FactoryBean
詳解BeanPostProcessor
作用和定義
常規 BeanPostProcessor 的作用是提供自定義的實體化邏輯、初始化邏輯、依賴關系決議邏輯等,對bean進行增強,主要的作用階段是初始階段前后,該介面定義了2介面,分別是前置增強和后置增強, Spring AOP 功能主要是通過 BeanPostProcessor 實作的,
public interface BeanPostProcessor {
// 在任何 bean 初始化回呼(如 InitializingBean 的 afterPropertiesSet 或自定義 init 方法)之前 // 將此 BeanPostProcessor 應用于給定的新 bean 實體, bean 將已填充屬性值,回傳的 bean 實體可能是 // 原始的包裝器,默認實作按原樣回傳給定的 bean
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
// 在任何 bean 初始化回呼(如 InitializingBean 的 afterPropertiesSet 或自定義 init 方法)之 // 后,將此 BeanPostProcessor 應用于給定的新 bean 實體, bean 將已填充屬性值,回傳的 bean 實體可 /// 能是原始的包裝器,默認實作按原樣回傳給定的 bean
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
自定義BeanPostProcessor
自定義一個 BeanPostProcessor 實作,該實作呼叫每個 bean 的 toString() 方法列印內容輸出到控制臺,
類定義如下
/**
* @author zfd
* @version v1.0
* @date 2022/1/20 11:21
* @關于我 請關注公眾號 螃蟹的Java筆記 獲取更多技術系列
*/
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 回傳原始bean
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println( bean + " 名稱是: " + beanName);
return bean;
}
}
@Component("xxxBeanOne")
public class BeanOne {
}
@Configuration
@ComponentScan
public class AppConfig {
}
測驗程式及結果
@org.junit.Test
public void test1() {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
BeanOne bean = context.getBean(BeanOne.class);
System.out.println(bean);
context.close();
}
// 結果
com.crab.spring.ioc.demo12.AppConfig$$EnhancerBySpringCGLIB$$da23c216@4445629 名稱是: appConfig
com.crab.spring.ioc.demo12.BeanOne@45b9a632 名稱是: xxxBeanOne
com.crab.spring.ioc.demo12.BeanOne@45b9a632
從結果看,MyBeanPostProcessor#postProcessAfterInitialization 輸出了容器內初始化bean的名稱,
使用 @Order 控制 BeanPostProcessor 執行順序
實際程式程式中肯定存在多個 BeanPostProcessor 通過 @Order 來指定順序,
增加 MyBeanPostProcessor2 指定順序是 -2
@Component
@Order(-2)
public class MyBeanPostProcessor2 implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 回傳原始bean
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("MyBeanPostProcessor2 輸出:" + bean + " 名稱是: " + beanName);
return bean;
}
}
同樣的測驗程式,觀察下結果
com.crab.spring.ioc.demo12.AppConfig$$EnhancerBySpringCGLIB$$6fb51f6@54c562f7 名稱是: appConfig
MyBeanPostProcessor2 輸出:com.crab.spring.ioc.demo12.AppConfig$$EnhancerBySpringCGLIB$$6fb51f6@54c562f7 名稱是: appConfig
com.crab.spring.ioc.demo12.BeanOne@318ba8c8 名稱是: xxxBeanOne
MyBeanPostProcessor2 輸出:com.crab.spring.ioc.demo12.BeanOne@318ba8c8 名稱是: xxxBeanOne
com.crab.spring.ioc.demo12.BeanOne@318ba8c8
MyBeanPostProcessor2 在 MyBeanPostProcessor 之前執行,
通過原始碼了解下Spring是如何將多個 BeanPostProcessor 排序的,對應 PostProcessorRegistrationDelegate#registerBeanPostProcessors(),
可以看出順序是: PriorityOrdered > Ordered > 其它常規的,
public static void registerBeanPostProcessors(
ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
// Separate between BeanPostProcessors that implement PriorityOrdered,
// Ordered, and the rest.
List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
List<BeanPostProcessor> internalPostProcessors = new ArrayList<>();
List<String> orderedPostProcessorNames = new ArrayList<>();
List<String> nonOrderedPostProcessorNames = new ArrayList<>();
for (String ppName : postProcessorNames) {
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
priorityOrderedPostProcessors.add(pp);
if (pp instanceof MergedBeanDefinitionPostProcessor) {
internalPostProcessors.add(pp);
}
}
else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
orderedPostProcessorNames.add(ppName);
}
else {
nonOrderedPostProcessorNames.add(ppName);
}
}
// First, register the BeanPostProcessors that implement PriorityOrdered.
sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);
// Next, register the BeanPostProcessors that implement Ordered.
List<BeanPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
for (String ppName : orderedPostProcessorNames) {
BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
orderedPostProcessors.add(pp);
if (pp instanceof MergedBeanDefinitionPostProcessor) {
internalPostProcessors.add(pp);
}
}
sortPostProcessors(orderedPostProcessors, beanFactory);
registerBeanPostProcessors(beanFactory, orderedPostProcessors);
// Now, register all regular BeanPostProcessors.
List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
for (String ppName : nonOrderedPostProcessorNames) {
BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
nonOrderedPostProcessors.add(pp);
if (pp instanceof MergedBeanDefinitionPostProcessor) {
internalPostProcessors.add(pp);
}
}
registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);
// Finally, re-register all internal BeanPostProcessors.
sortPostProcessors(internalPostProcessors, beanFactory);
registerBeanPostProcessors(beanFactory, internalPostProcessors);
}
AutowiredAnnotationBeanPostProcessor 分析
將回呼介面或注釋與自定義BeanPostProcessor 實作結合使用是擴展 Spring IoC 容器的常用方法,`AutowiredAnnotationBeanPostProcesso由 Spring 提供的實作,并自動注入依賴到帶注解的欄位、setter 方法和任意配置方法,
原始碼頭說明: 自動裝配帶注釋的欄位、設定方法和任意配置方法的 BeanPostProcessor 實作,此類要注入的成員是通過注解檢測的:默認情況下,Spring 的@Autowired 和@Value 注解,還支持 JSR-330 的 @Inject 注解(如果可用)作為 Spring 自己的 @Autowired 的直接替代品,
來看一下原始碼里面的關鍵方法
public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
implements MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {
// 關鍵方法1: 默認構造方法 默認值支持注解 @Autowired @Value @Inject
public AutowiredAnnotationBeanPostProcessor() {
this.autowiredAnnotationTypes.add(Autowired.class);
this.autowiredAnnotationTypes.add(Value.class);
try {
this.autowiredAnnotationTypes.add((Class<? extends Annotation>)
ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
logger.trace("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring");
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}
// 關鍵方法2: 將支持的注解標準的內容合并到 BeanDefinition 中為后續設定屬性值做準備
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
InjectionMetadata metadata = https://www.cnblogs.com/kongbubihai/archive/2022/02/16/findAutowiringMetadata(beanName, beanType, null);
metadata.checkConfigMembers(beanDefinition);
}
// 關鍵方法3: 自動注入依賴屬性
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
metadata.inject(bean, beanName, pvs);
}
catch (BeanCreationException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(beanName,"Injection of autowired dependencies failed", ex);
}
return pvs;
}
}
既然簡單提到原始碼了,深入一下,結合Spring的生命周期,看下對應的生效位置
關鍵方法2: 將支持的注解標準的內容合并到 BeanDefinition,實體化之后屬性填充之前,


關鍵方法3: 自動注入依賴屬性,發生在屬性賦值階段

類似的 BeanPostProcessor 匯總下,可以對應看下原始碼
| 名稱 | 作用 |
|---|---|
| ConfigurationClassPostProcessor | 處理@Configuration |
| CommonAnnotationBeanPostProcessor | 處理JSR 250 中的 @PostConstruct,@PreDestroy和@Resource |
| AutowiredAnnotationBeanPostProcessor | 處理@Autowired 、@Value 、 JSR-330 的 @Inject |
| PersistenceAnnotationBeanPostProcessor | 處理 PersistenceUnit 和 PersistenceContext 注解的 BeanPostProcessor,用于注入對應的 JPA 資源 EntityManagerFactory 和 EntityManager, |
詳解BeanFactoryPostProcessor
BeanPostProcessor 是對bean實體進行增強,類似地,BeanFactoryPostProcessor 對 bean 配置元資料也就是 BeanDefinition 進行操作,Spring IoC 容器允許BeanFactoryPostProcessor讀取配置元資料并可能在容器實體化任何 bean之前BeanFactoryPostProcessor更改它,當然不包括 BeanFactoryPostProcessor實體,Spring 包括許多預定義的 BeanFactoryPostProcessor ,例如 PropertyOverrideConfigurer 和 PropertySourcesPlaceholderConfigurer,
多個 BeanFactoryPostProcessor 的屬性可以通過 @Ordered 來控制,
PropertySourcesPlaceholderConfigurer 分析
可以使用 PropertySourcesPlaceholderConfigurer 通過使用標準 Java 屬性格式將 bean 定義中的屬性值外部化到一個單獨的檔案中,針對當前 Spring 環境及其一組 PropertySource 決議 bean 定義屬性值和 @Value 注釋中的 ${...} 占位符,
PropertySourcesPlaceholderConfigurer 不僅在指定的屬性檔案中尋找屬性,默認情況下,如果它不能在指定的屬性檔案中找到屬性,它將檢查Spring Environment屬性和常規Java System屬性,
來看一個實際的場景: 通常的我們的資料庫的連接資訊是配置在組態檔中的而不是固定寫在程式中,
資料庫組態檔 jdbc.properties
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
通過 @Value 注入到我們資料庫配置類中
@Configuration("myDataSource")
public class MyDataSource {
@Value("${jdbc.driverClassName}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
// ...
}
看下配置類,注入一個 PropertySourcesPlaceholderConfigurer
@Configuration
@ComponentScan
// @PropertySource("classpath:demo12/jdbc.properties")
public class AppConfig2 {
// 自定義一個 PropertySourcesPlaceholderConfigurer
@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
PropertySourcesPlaceholderConfigurer configurer =
new PropertySourcesPlaceholderConfigurer();
// 配置組態檔的路徑
configurer.setLocation(new ClassPathResource("demo12/jdbc.properties"));
return configurer;
}
}
等價于下面的xml配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean >
<property name="locations" value="https://www.cnblogs.com/kongbubihai/archive/2022/02/16/classpath:demo12/jdbc.properties"/>
</bean>
<bean >
<property name="driverClassName" value="https://www.cnblogs.com/kongbubihai/archive/2022/02/16/${jdbc.driverClassName}"/>
<property name="url" value="https://www.cnblogs.com/kongbubihai/archive/2022/02/16/${jdbc.url}"/>
<property name="username" value="https://www.cnblogs.com/kongbubihai/archive/2022/02/16/${jdbc.username}"/>
<property name="password" value="https://www.cnblogs.com/kongbubihai/archive/2022/02/16/${jdbc.password}"/>
</bean>
</beans>
觀察下輸出結果
MyDataSource{driverClassName='org.hsqldb.jdbcDriver', url='jdbc:hsqldb:hsql://production:9002', username='sa', password='root'}
配置在 MyDataSource 的外部屬性值已經成功注入了,
過多關于 @Value 和外部配置屬性的注入可以看下之前的文章 :Spring系列12: @Value @Resource @PostConstruct @PreDestroy 詳解,
擴展: 思考下Springboot 可以使用 ${xxx.xxx} 參考外部的 application.yml或是application.properties,是不是注入了一個自定義的 PropertySourcesPlaceholderConfigurer?
PropertyOverrideConfigurer詳解
類似于 PropertySourcesPlaceholderConfigurer , PropertyOverrideConfigurer 用于覆寫bean定義中的屬性值,如果覆寫屬性檔案沒有特定 bean 屬性值,則使用默認背景關系定義的,覆寫屬性檔案的格式如下:
# bean定義名稱.屬性名稱=覆寫的屬性值
beanName.property=value
來一個案例,在上面的案例的基礎上,myDataSource.username替換為sa-override,
myDataSource.password替換為root-override,
組態檔如下
myDataSource.username=sa-override
myDataSource.password=root-override
配置類注入一個 PropertySourcesPlaceholderConfigurer
@Configuration
@ComponentScan
// @PropertySource("classpath:demo12/jdbc.properties")
public class AppConfig2 {
// 自定義一個 PropertySourcesPlaceholderConfigurer
@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
PropertySourcesPlaceholderConfigurer configurer =
new PropertySourcesPlaceholderConfigurer();
configurer.setLocation(new ClassPathResource("demo12/jdbc.properties"));
return configurer;
}
// 注入一個 PropertyOverrideConfigurer
@Bean
public static PropertyOverrideConfigurer propertyOverrideConfigurer() {
PropertyOverrideConfigurer overrideConfigurer = new PropertyOverrideConfigurer();
overrideConfigurer.setLocation(new ClassPathResource("demo12/jdbc-override.properties"));
return overrideConfigurer;
}
}
觀察下輸出結果
MyDataSource{driverClassName='org.hsqldb.jdbcDriver', url='jdbc:hsqldb:hsql://production:9002', username='sa-override', password='root-override'}
username 和 password 已經成功被替換了,
詳解FactoryBean
FactoryBean 介面是Spring IoC容器實體化邏輯的可插點,如果您有復雜的初始化代碼,用Java更好地表達,而不是(可能)冗長的XML,那么您可以創建自己的FactoryBean,在該類中撰寫復雜的初始化代碼,然后將定制的FactoryBean 插入到容器中,
FactoryBean 概念和介面在 Spring 框架中的許多地方都使用, Spring 本身附帶了 50 多個 FactoryBean 介面的實作,
FactoryBean 介面的定義如下
public interface FactoryBean<T> {
// 回傳此工廠創建的物件的實體,該實體可能會被共享,具體取決于該工廠是回傳單例還是原型,
T getObject() throws Exception;
// 回傳由 getObject() 方法回傳的物件型別,如果事先不知道該型別,則回傳 null,
Class<?> getObjectType();
// 如果此 FactoryBean 回傳單例,則回傳 true,否則回傳 false,此方法的默認實作回傳 true,
default boolean isSingleton() {
return true;
}
}
FactoryBean注入到Spring容器中產生2個實體,FactoryBean實體和其生產處理的實體假設id是 myBean ,那么如何獲取這個2個bean?
- 獲取FactoryBean實體: ApplicationContext#getBean("&myBean")
- 獲取生產出來的bean實體:ApplicationContext#getBean("myBean")
來個實戰案例
定義一個 MyFactoryBean ,生產非單例,每次new一個
@Component("myFactoryBean")
public class MyFactoryBean implements FactoryBean<BeanOne> {
@Override
public BeanOne getObject() throws Exception {
// 每次生成一個
return new BeanOne();
}
@Override
public Class<?> getObjectType() {
return BeanOne.class;
}
@Override
public boolean isSingleton() {
// 非單例 每次生成一個
return false;
}
}
觀察下輸出
MyFactoryBean 實體: com.crab.spring.ioc.demo12.MyFactoryBean@2ef3eef9
true
com.crab.spring.ioc.demo12.BeanOne@71809907 名稱是: myFactoryBean
MyBeanPostProcessor2 輸出:com.crab.spring.ioc.demo12.BeanOne@71809907 名稱是: myFactoryBean
com.crab.spring.ioc.demo12.BeanOne@3ce1e309 名稱是: myFactoryBean
MyBeanPostProcessor2 輸出:com.crab.spring.ioc.demo12.BeanOne@3ce1e309 名稱是: myFactoryBean
MyFactoryBean 生產的bean實體: com.crab.spring.ioc.demo12.BeanOne@71809907
false
細心的人會發現呼叫 FactoryBean#getObject() 生產bean 也是會走 BeanPostProcessor的增強流程的,
在深入了解下使用 FactoryBean 的注意點:
- FactoryBean 是一種程式化契約,實作不應該依賴注解驅動的注入或其他反射設施, getObjectType() getObject() 可能會在Spring引導程序的早期呼叫,甚至在任何后處理器設定之前,如果需要訪問其他 bean可以實作 BeanFactoryAware 并以編程方式獲取它們,
- 容器只負責管理FactoryBean 實體的生命周期,而不是FactoryBean 創建的物件的生命周期,因此,暴露的 bean 物件(例如 java.io.Closeable.close() 上的銷毀方法不會被自動呼叫,相反,FactoryBean 應該實作 DisposableBean 并將任何此類關閉呼叫委托給底層物件,
類似的介面還有 org.springframework.aop.framework.ProxyFactoryBean 、SmartFactoryBean 感興趣的可以了解下
總結
本文詳細分析了Spring 容器的擴展點,包括BeanPostProcessor、BeanFactoryPostProcessor、FactoryBean的原理和使用,結合上一篇Spring的生命周期和回呼理解會更好,完全消化這2篇內容,Spring開發會更加順手,離成為Spring高手更進一步,
本篇原始碼地址: https://github.com/kongxubihai/pdf-spring-series/tree/main/spring-series-ioc/src/main/java/com/crab/spring/ioc/demo12
知識分享,轉載請注明出處,學無先后,達者為先!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/424923.html
標籤:其他
上一篇:JVM的垃圾回識訓制
