深入學習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方法,則呼叫它,如果實現了自定義的銷燬方法,則呼叫之。