摘要:在本篇文章中,我們將深入探討Spring框架中的重要組件——BeanPostProcessor,首先,我們將了解其設計理念和目標,然后通過實際的例子學習如何基礎使用它,如何通過BeanPostProcessor改變Bean的初始化結果以及如何利用它修改Bean的屬性,
本文分享自華為云社區《Spring高手之路6——Bean生命周期的擴展點:BeanPostProcessor》,作者:磚業洋__ ,
1. 探索Spring的后置處理器(BeanPostProcessor)
1.1 BeanPostProcessor的設計理念
BeanPostProcessor的設計目標主要是提供一種擴展機制,讓開發者可以在Spring Bean的初始化階段進行自定義操作,這種設計理念主要體現了Spring的一種重要原則,即“開放封閉原則”,開放封閉原則強調軟體物體(類、模塊、函式等等)應該對于擴展是開放的,對于修改是封閉的,在這里,Spring容器對于Bean的創建、初始化、銷毀等生命周期進行了管理,但同時開放了BeanPostProcessor這種擴展點,讓開發者可以在不修改Spring原始碼的情況下,實作對Spring Bean生命周期的自定義操作,這種設計理念大大提升了Spring的靈活性和可擴展性,
BeanPostProcessor不是Spring Bean生命周期的一部分,但它是在Spring Bean生命周期中起重要作用的組件,
1.2 BeanPostProcessor的檔案說明
我們來看看這個方法的檔案注釋,從圖中可以看到,BeanPostProcessor 介面定義了兩個方法,postProcessBeforeInitialization和postProcessAfterInitialization
postProcessBeforeInitialization方法會在任何bean初始化回呼(如InitializingBean的afterPropertiesSet方法或者自定義的init-method)之前被呼叫,也就是說,這個方法會在bean的屬性已經設定完畢,但還未進行初始化時被呼叫,
postProcessAfterInitialization方法在任何bean初始化回呼(比如InitializingBean的afterPropertiesSet或者自定義的初始化方法)之后被呼叫,這個時候,bean的屬性值已經被填充完畢,回傳的bean實體可能是原始bean的一個包裝,
2. BeanPostProcessor的使用
2.1 BeanPostProcessor的基礎使用示例
全部代碼如下:
首先定義兩個簡單的Bean:Lion和Elephant
Lion.java
package com.example.demo.bean; public class Lion { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
Elephant.java
package com.example.demo.bean; public class Elephant { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
然后定義一個簡單的BeanPostProcessor,它只是列印出被處理的Bean的名字:
package com.example.demo.processor; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("Before initialization: " + beanName); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("After initialization: " + beanName); return bean; } }
接著我們定義一個配置類,其中包含對Lion、Elephant類和MyBeanPostProcessor類的Bean定義:
package com.example.demo.configuration; import com.example.demo.bean.Elephant; import com.example.demo.bean.Lion; import com.example.demo.processor.MyBeanPostProcessor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class AnimalConfig { @Bean public Lion lion() { return new Lion(); } @Bean public Elephant elephant() { return new Elephant(); } @Bean public MyBeanPostProcessor myBeanPostProcessor() { return new MyBeanPostProcessor(); } }
最后,我們在主程式中創建ApplicationContext物件:
import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class Main { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(AnimalConfig.class); ((AnnotationConfigApplicationContext)context).close(); } }
運行結果:
以上代碼在執行時,將先創建Lion和Elephant物件,然后在初始化程序中和初始化后呼叫postProcessBeforeInitialization和postProcessAfterInitialization方法,列印出被處理的Bean的名字,
細心的小伙伴可能觀察到這里有紅色日志
資訊: Bean 'animalConfig' of type [com.example.demo.configuration.AnimalConfig$$EnhancerBySpringCGLIB$$ee4adc7e] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
在Spring中,BeanPostProcessor是被特殊處理的,它們會在其他普通Bean之前被實體化和初始化,這樣設計的原因是BeanPostProcessor的存在可以影響其他Bean的創建和初始化程序, Spring應用背景關系中可以存在多個BeanPostProcessor,Spring本身就提供了很多內置的BeanPostProcessor,
但是,如果在初始化BeanPostProcessor的程序中需要依賴其他的Bean,那么這些被依賴的Bean會先于后置處理器進行初始化,然而,由于這些被依賴的Bean是在該BeanPostProcessor初始化完成之前就已經進行了初始化,它們就會錯過這個BeanPostProcessor的處理,在這個例子中,MyBeanPostProcessor就是這樣的一個BeanPostProcessor,而"animalConfig"是它所依賴的Bean,所以這個日志資訊就是說,'animalConfig'這個Bean在初始化的時候,沒有被所有的BeanPostProcessor處理,這里它無法得到MyBeanPostProcessor的處理,
我們只需要把實體化程序直接交給Spring容器來管理,而不是在配置類中手動進行實體化,就可以消除這個提示資訊,也就是在MyBeanPostProcessor上加@Component即可,
在第3節的例子中就使用了@Component處理這個MyBeanPostProcessor,這個提示就消失了,
2.2 利用BeanPostProcessor修改Bean的初始化結果的回傳值
還是上面的例子,我們只修改一下MyBeanPostProcessor 類的方法后再次運行
package com.example.demo.processor; import com.example.demo.bean.Elephant; import com.example.demo.bean.Lion; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("Before initialization: " + bean); if (bean instanceof Lion) { return new Elephant(); } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("After initialization: " + bean); return bean; } }
運行結果:
BeanPostProcessor的兩個方法都可以回傳任意的Object,這意味著我們可以在這兩個方法中更改回傳的bean,例如,如果我們讓postProcessBeforeInitialization方法在接收到Lion實體時回傳一個新的Elephant實體,那么我們將會看到Lion實體變成了Elephant實體,
那既然BeanPostProcessor的兩個方法都可以回傳任意的Object,那我搞點破壞回傳null會怎么樣,會不會因為初始化bean為null而導致例外呢?
答案是不會的,我們來看一下:
package com.example.demo.processor; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("Before initialization: " + bean); return null; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("After initialization: " + bean); return bean; } }
我們運行看結果
結果發現還是正常初始化的bean型別,不會有任何改變,我們繼續除錯看看是為什么
我們通過堆疊幀看到呼叫postProcessBeforeInitialization方法的上一個方法是applyBeanPostProcessorsBeforeInitialization,雙擊點開看一看這個方法
從我這個除錯圖中可以看到,如果postProcessBeforeInitialization回傳null,Spring仍然用原始的bean進行后續的處理,同樣的邏輯在postProcessAfterInitialization也是一樣,這就是為什么我們在BeanPostProcessor類的方法中回傳null,原始bean實體還是存在的原因,
2.3 通過BeanPostProcessor實作Bean屬性的動態修改
來看看是怎么攔截 bean 的初始化的
全部代碼如下:
首先,我們定義一個Lion類:
public class Lion { private String name; public Lion() { this.name = "Default Lion"; } public Lion(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Lion{" + "name='" + name + '\'' + '}'; } }
接下來,我們定義一個BeanPostProcessor,我們稱之為MyBeanPostProcessor :
package com.example.demo.processor; import com.example.demo.bean.Lion; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("Bean的初始化之前:" + bean); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("Bean的初始化之后:" + bean); if (bean instanceof Lion) { ((Lion) bean).setName("Simba"); } return bean; } }
然后我們定義一個配置類,其中包含對Lion類的Bean定義和對MyBeanPostProcessor 類的Bean定義:
package com.example.demo.configuration; import com.example.demo.bean.Lion; import com.example.demo.processor.MyBeanPostProcessor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class AnimalConfig { @Bean public Lion lion() { return new Lion(); } @Bean public MyBeanPostProcessor myBeanPostProcessor() { return new MyBeanPostProcessor(); } }
最后,我們在主程式中創建ApplicationContext物件,并獲取Lion物件:
package com.example.demo; import com.example.demo.bean.Lion; import com.example.demo.configuration.AnimalConfig; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class DemoApplication { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(AnimalConfig.class); Lion lion = context.getBean("lion", Lion.class); System.out.println(lion); ((AnnotationConfigApplicationContext)context).close(); } }
運行結果:
上面代碼在執行時,先創建一個Lion物件,然后在初始化程序中和初始化后呼叫postProcessBeforeInitialization和postProcessAfterInitialization方法,修改Lion的名字為"Simba",最后在主程式中輸出Lion物件,顯示其名字為"Simba",
3. 深度剖析BeanPostProcessor的執行時機
3.1 后置處理器在Bean生命周期中的作用及執行時機
在這個例子中,我們將創建一個名為Lion和Elephant 的Bean,它會展示屬性賦值和生命周期的各個步驟的執行順序,同時,我們還將創建一個BeanPostProcessor來列印訊息并顯示它的執行時機,
全部代碼如下:
首先,我們定義我們的Lion:
package com.example.demo.bean; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.annotation.Resource; public class Lion implements InitializingBean, DisposableBean { private String name; private Elephant elephant; public Lion() { System.out.println("1. Bean Constructor Method Invoked!"); } public String getName() { return name; } public void setName(String name) { this.name = name; System.out.println("2. Bean Setter Method Invoked! name: " + name); } /** * setter注入 * @param elephant */ @Resource public void setElephant(Elephant elephant) { this.elephant = elephant; System.out.println("2. Bean Setter Method Invoked! elephant: " + elephant); } @PostConstruct public void postConstruct() { System.out.println("4. @PostConstruct Method Invoked!"); } @Override public void afterPropertiesSet() throws Exception { System.out.println("5. afterPropertiesSet Method Invoked!"); } public void customInitMethod() { System.out.println("6. customInitMethod Method Invoked!"); } @PreDestroy public void preDestroy() { System.out.println("8. @PreDestroy Method Invoked!"); } @Override public void destroy() throws Exception { System.out.println("9. destroy Method Invoked!"); } public void customDestroyMethod() { System.out.println("10. customDestroyMethod Method Invoked!"); } }
創建Lion所依賴的Elephant
package com.example.demo.bean; import org.springframework.stereotype.Component; @Component public class Elephant { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
然后,我們定義一個簡單的BeanPostProcessor:
package com.example.demo.processor; import com.example.demo.bean.Lion; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.stereotype.Component; @Component public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if(bean instanceof Lion) { System.out.println("3. postProcessBeforeInitialization Method Invoked!"); } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if(bean instanceof Lion) { System.out.println("7. postProcessAfterInitialization Method Invoked!"); } return bean; } }
創建一個配置類AnimalConfig
package com.example.demo.configuration; import com.example.demo.bean.Lion; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class AnimalConfig { @Bean(initMethod = "customInitMethod", destroyMethod = "customDestroyMethod") public Lion lion() { Lion lion = new Lion(); lion.setName("my lion"); return lion; } }
主程式:
package com.example.demo; import com.example.demo.bean.Lion; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class DemoApplication { public static void main(String[] args) { System.out.println("容器初始化之前..."); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.example"); System.out.println("容器初始化完成"); Lion bean = context.getBean(Lion.class); bean.setName("oh!!! My Bean set new name"); System.out.println("容器準備關閉..."); context.close(); System.out.println("容器已經關閉"); } }
控制臺上看到所有的方法呼叫都按照預期的順序進行,這可以更好地理解Bean屬性賦值和生命周期以及BeanPostProcessor的作用,
根據列印日志我們可以分析出
- 首先,Bean Constructor Method Invoked! 表明 Lion 的構造器被呼叫,創建了一個新的 Lion 實體,
- 接著,Bean Setter Method Invoked! name: my lion 和 Bean Setter Method Invoked! elephant: com.example.demo.bean.Elephant@7364985f 說明 Spring 對 Lion 實體的依賴注入,在這一步,Spring 呼叫了 Lion 的 setter 方法,為 name 屬性設定了值 “my lion”,同時為 elephant 屬性注入了一個 Elephant 實體,
- 然后,postProcessBeforeInitialization Method Invoked! 說明 MyBeanPostProcessor 的 postProcessBeforeInitialization 方法被呼叫,這是在初始化 Lion 實體之前,
- @PostConstruct Method Invoked! 說明 @PostConstruct 注解的方法被呼叫,這是在 Bean 初始化之后,但是在 Spring 執行任何進一步初始化之前,
- afterPropertiesSet Method Invoked! 說明 Spring 呼叫了 InitializingBean 的 afterPropertiesSet 方法
- customInitMethod Method Invoked! 表示呼叫了 Lion 實體的 init-method 方法,
- postProcessAfterInitialization Method Invoked! 說明 MyBeanPostProcessor 的 postProcessAfterInitialization 方法被呼叫,這是在初始化 Lion 實體之后,
然后 Spring 完成了整個初始化程序, - 主程式中手動呼叫了 Lion 實體的 setter 方法,因此在 Bean Setter Method Invoked! name: oh!!! My Bean set new name 可見,name 屬性被設定了新的值 "oh!!! My Bean set new name",
當容器準備關閉時: - @PreDestroy Method Invoked! 說明 @PreDestroy 注解的方法被呼叫,這是在 Bean 銷毀之前,
- destroy Method Invoked! 表示 Lion 實體開始銷毀,在這一步,Spring 呼叫了 DisposableBean 的 destroy 方法,
- customDestroyMethod Method Invoked! 表示 Lion 實體開始銷毀,呼叫了Lion 實體的 destroy-method 方法,
最后,Spring 完成了整個銷毀程序,容器關閉,
這個日志提供了 Spring Bean 生命周期的完整視圖,顯示了從創建到銷毀程序中的所有步驟,
注意:DisposableBean 的 destroy 方法和 destroy-method 方法呼叫,這個銷毀程序不意味著bean實體就被立即從記憶體中洗掉了,Java的垃圾收集機制決定了物件什么時候被從記憶體中洗掉,Spring容器無法強制進行這個操作,比如解除bean之間的關聯和清理快取,這并不是Spring在銷毀bean時會做的,而是由Java的垃圾回收器在一個物件不再被參考時做的事情,
BeanPostProcessor 的執行順序是在 Spring Bean 的生命周期中非常重要的一部分,例如,如果一個 Bean 實作了 InitializingBean 介面,那么 afterPropertiesSet 方法會在所有的 BeanPostProcessor 的 postProcessBeforeInitialization 方法之后呼叫,以確保所有的前置處理都完成了,同樣,BeanPostProcessor 的 postProcessAfterInitialization 方法會在所有的初始化回呼方法之后呼叫,以確保 Bean 已經完全初始化了,
我們可以注冊多個 BeanPostProcessor,在這種情況下,Spring 會按照它們的 Ordered 介面或者 @Order 注解指定的順序來呼叫這些后置處理器,如果沒有指定順序,那么它們的執行順序是不確定的,
3.2 圖解:Bean生命周期與后置處理器的互動時序
綜合上面的執行結果,我們來總結一下,下面是Spring Bean生命周期的時序圖,它詳細地描繪了Spring Bean從實體化到準備使用的整個程序,包括Bean的實體化、屬性賦值、生命周期方法的執行和后置處理器的呼叫,
點擊關注,第一時間了解華為云新鮮技術~
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/556255.html
標籤:架構設計
上一篇:說說設計模式~委派模式
下一篇:返回列表
