深入學習Spring Bean生命週期

語言: CN / TW / HK

highlight: atom-one-dark theme: vuepress


小知識,大挑戰!本文正在參與“程序員必備小知識”創作活動

Bean的初始化和銷燬

在整個生命週期過程中,我們可以自定義Bean的初始化和銷燬鈎子函數,當Bean的生命週期到達相應的階段的時候,Spring會調用我們自定義的Bean的初始化和銷燬方法。自定義Bean初始化和銷燬方法有多種方式,下面逐一介紹。

@Bean

上一節中介紹了可以在配置類中通過@Bean註解來註冊Bean,我們也可以通過它來指定Bean的初始化和方法。

為了演示,我們新建一個Spring Boot項目,然後創建一個User類:

java public class User { public User() { System.out.println("調用無參構造器創建User"); } public void init() { System.out.println("初始化User"); } public void destory() { System.out.println("銷燬User"); } }

然後在配置類裏註冊該組件,並指定初始化和銷燬方法:

```java @Configuration public class WebConfig {

@Bean(initMethod = "init", destroyMethod = "destory")
public User user() {
    return new User();
}

} ```

其中initMethod = "init"destroyMethod = "destory"與User類裏的initdestory方法相對應。

在Spring Boot入口類中測試:

java // 返回 IOC 容器,使用註解配置,傳入配置類 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class); User user = context.getBean(User.class); // 關閉 IOC 容器 context.close();

啟動項目,觀察控制枱輸出:

sl01.png

從上面的輸出我們看出在容器啟動之前,先調用對象的無參構造器創建對象,然後調用初始化方法,在容器關閉的時候調用銷燬方法。

上面的情況是對於單例而言的,如果組件是多例模式又是什麼情況呢?我們把上面的組件註冊配置改為多例,然後再次啟動項目,觀察控制枱輸出:

sl02.png

在多例模式下,IOC容器啟動的時候並不會去創建對象,而是在每次獲取的時候才會去調用方法創建對象,創建完對象後再調用初始化方法。

但在容器關閉後,Spring並沒有調用相應的銷燬方法,這是因為在多例模式下,容器不會管理這個組件(只負責在你需要的時候創建這個組件),所以容器在關閉的時候並不會調用相應的銷燬方法。

InitializingBean&DisposableBean

除了上面這種方式指定初始化和銷燬方法外,Spring還為我們提供了和初始化,銷燬相對應的接口: - InitializingBean接口包含一個afterPropertiesSet方法,我們可以通過實現該接口,然後在這個方法中編寫初始化邏輯。 - DisposableBean接口包含一個destory方法,我們可以通過實現該接口,然後再這個方法中編寫銷燬邏輯。

新建一個類,名稱為Bird,然後實現這兩個接口:

```java public class Bird implements InitializingBean, DisposableBean { public Bird() { System.out.println("調用無參構造器創建Bird"); }

@Override
public void destroy() {
    System.out.println("銷燬Bird");
}

@Override
public void afterPropertiesSet() {
    System.out.println("初始化Bird");
}

} 在配置類中註冊這個組件:java @Bean public Bird bird() { return new Bird(); } 測試一波:java AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class); System.out.println("容器創建完畢"); context.close(); ```

啟動項目,觀察控制枱輸出:

sl03.png

@PostConstruct & @PreDestroy

除了上面兩種指定初始化和銷燬方法的方式外,我們還可以使用@PostConstruct@PreDestroy註解修飾方法來指定相應的初始化和銷燬方法。

新建一個類,名稱為Fish:

```java public class Fish { public Fish() { System.out.println("調用無參構造器創建Fish"); }

@PostConstruct
public void init() {
    System.out.println("初始化Fish");
}

@PreDestroy
public void destory() {
    System.out.println("銷燬Fish");
}

} ```

在配置類中這個組件:

java @Bean public Fish fish(){ return new Fish(); }

測試一波:

java AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class); System.out.println("容器創建完畢"); context.close();

啟動項目,觀察控制枱輸出:

sl04.png

效果和上面兩種方式一致。

這兩個註解並非Spring提供,而是JSR250規範提供。

BeanPostProcessor

Spring提供了一個BeanPostProcessor接口,俗稱Bean後置通知處理器,它提供了兩個方法postProcessBeforeInitializationpostProcessAfterInitialization。其中postProcessBeforeInitialization在組件的初始化方法調用之前執行,postProcessAfterInitialization在組件的初始化方法調用之後執行。它們都包含兩個入參:

  1. bean:當前組件對象;
  2. beanName:當前組件在容器中的名稱。

兩個方法都返回一個Object類型,我們可以直接返回當前組件對象,或者包裝後返回。

我們來定義一個BeanPostProcessor接口的實現類MyBeanPostProcessor

java public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println(beanName + " 初始化之前調用"); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println(beanName + " 初始化之後調用"); return bean; } }

在配置類中註冊該組件:

java @Bean public MyBeanPostProcessor myBeanPostProcessor () { return new MyBeanPostProcessor(); }

再次啟動項目,觀察控制枱輸出:

sl05.png

sl06.png

總結

主要把握創建過程和銷燬過程這兩個大的方面

創建過程:首先實例化Bean,並設置Bean的屬性,根據其實現的Aware接口(主要是BeanFactoryAware接口,BeanFactoryAware,ApplicationContextAware)設置依賴信息,接下來調用BeanPostProcess的postProcessBeforeInitialization方法,完成initial前的自定義邏輯;afterPropertiesSet方法做一些屬性被設定後的自定義的事情;調用Bean自身定義的init方法,去做一些初始化相關的工作;然後再調用postProcessAfterInitialization去做一些bean初始化之後的自定義工作。這四個方法的調用有點類似AOP。 此時,Bean初始化完成,可以使用這個Bean了。

銷燬過程:如果實現了DisposableBean的destroy方法,則調用它,如果實現了自定義的銷燬方法,則調用之。