Spring Bean 的生命週期

前言

本文主要介紹 Spring IoC 容器如何管理 Bean 的生命週期。
在應用開發中,常常需要執行一些特定的初始化工作,這些工作都是相對比較固定的,比如建立數據庫連接,打開網絡連接等,同時,在結束服務時,也有一些相對固定的銷燬工作需要執行。爲了便於這些工作的設計,Spring IoC 容器提供了相關的功能,可以讓應用定製 Bean 的初始化和銷燬過程。

Bean 生命週期

在這裏插入圖片描述
Bean 的生命週期可分爲初始化階段和銷燬階段

初始化階段

  • 啓動 Spring IoC 容器

  • 實例化
    調用 Bean 的構造方法創建一個對象,這個對象默認是單例的。

  • 設置屬性
    調用 Bean 對象的 setter 方法設置屬性值。

  • Aware 相關的屬性,注入到 Bean 對象

    • 設置 Bean 名稱
      如果 Bean 實現了 BeanNameAware 接口,調用其重寫的 #setBeanName(String name) 方法,將 bean 標籤中的 id 或 @Bean 註解的 name 屬性(若沒設置 name,則默認爲方法名)設置爲 Bean 的名稱。
    • 設置 Bean 工廠
      如果 Bean 實現了 BeanFactoryAware 接口,調用其重寫的 #setBeanFactory(BeanFactory factory) 方法,設置 Bean 工廠。
    • 設置上下文
      調用 ApplicationContextAware#setApplicationContext(ApplicationContext context) 方法。
  • 調用其他接口的方法,進一步初始化 Bean 對象

    • 如果存在與 Bean 關聯的任何 BeanPostProcessor 實現類,則調用 #preProcessBeforeInitialization(Object bean, String beanName) 方法。
    • 如果 Bean 實現了 InitializingBean 接口,則會調用 #afterPropertiesSet() 方法。
      如果爲 Bean 指定了 init 方法(例如 <bean />init-method 屬性或 @Bean 註解的 initMethod 屬性),那麼將調用該方法。
    • 如果存在與 Bean 關聯的任何 BeanPostProcessor 實現類,則將調用 #postProcessAfterInitialization(Object bean, String beanName) 方法。

銷燬階段

  • 如果 Bean 實現了 DisposableBean 接口,當 Spring 容器關閉時,會調用 #destroy() 方法。
  • 如果爲 Bean 指定了 destroy 方法(例如 <bean />destroy-method 屬性或 @Bean 註解的 destroyMethod 屬性),那麼將調用該方法。

代碼實例

自定義 Bean

@Data
@Slf4j
public class Person implements BeanNameAware, BeanFactoryAware, InitializingBean, ApplicationContextAware, DisposableBean {

    private String name;

    public Person() {
        log.info("constructor: Instantiate");
    }

    public void setName(String name) {
        log.info("setter: populate property");
        this.name = name;
    }

    public String getName() {
        log.info("getter");
        return name;
    }

    @Override
    public void setBeanName(String beanName) {
        log.info("setBeanName: " + beanName);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        log.info("setBeanFactory");
    }

    @Override
    public void afterPropertiesSet() {
        log.info("afterPropertiesSet");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        log.info("setApplicationContext");
    }

    @Override
    public void destroy() {
        log.info("destroy");
    }

    public void customInit() {
        log.info("customInit");
    }

    public void customDestroy() {
        log.info("customDestroy");
    }
}

代碼中自定義了一個 Bean 類 Person,實現以下接口並重寫其中的方法:

interface method
BeanNameAware setBeanName
BeanFactoryAware setBeanFactory
InitializingBean afterPropertiesSet
ApplicationContextAware setApplicationContext
DisposableBean destroy

另外,爲 Person 加入無參構造方法、getter/setter 方法。對這些方法全部加入日誌,將當前方法名打印出來,便於在控制檯觀察順序。

配置 Bean

@Configuration
public class PersonConfig {

    @Bean(initMethod = "customInit", destroyMethod = "customDestroy")
    public Person singletonPerson() {
        Person singletonPerson = new Person();
        singletonPerson.setName("Singleton Jake");
        return singletonPerson;
    }

    @Bean(initMethod = "customInit", destroyMethod = "customDestroy")
    @Lazy
    public Person lazySingletonPerson() {
        Person lazySingletonPerson= new Person();
        lazySingletonPerson.setName("Lazy Singleton Jake");
        return lazySingletonPerson;
    }

    @Bean(initMethod = "customInit", destroyMethod = "customDestroy")
    @Scope("prototype")
    public Person prototypePerson() {
        Person prototypePerson = new Person();
        prototypePerson.setName("Prototype Jake");
        return prototypePerson;
    }
}

上述代碼爲註冊了三個 bean,則 IoC 容器中會存在三個類型相同,名稱和作用範圍不同的 bean。

classType beanName scope
1 Person singletonPerson singleton
2 Person lazySingletonPerson singleton
3 Person prototypePerson prototype

自定義 BeanPostProcessor

@Component
@Slf4j
public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof Person) {
            log.info("postProcessBeforeInitialization, beanName = " + beanName);
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof Person) {
            log.info("postProcessAfterInitialization, beanName = " + beanName);
        }
        return bean;
    }
}

BeanPostProcessor 允許 IoC 容器在 bean 初始化前後進行處理

啓動 Application

注意:想觀察 IoC 容器關閉(Bean 銷燬)後的打印內容,在 pom 文件中不要加上 spring-boot-starter-web 依賴。
運行 Application 類中的 main 方法,控制檯的打印如下(僅截取關鍵部分):

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.4.RELEASE)

2020-03-02 17:41:22.111  INFO 406860 --- [           main] c.j.s.l.SpringLifecycleApplication       : Starting SpringLifecycleApplication on V12CNSSZ01MGRPE with PID 406860 (C:\wengzhengkai\idea-projects\spring-lifecycle\target\classes started by 60055807 in C:\wengzhengkai\idea-projects\spring-lifecycle)
2020-03-02 17:41:22.117  INFO 406860 --- [           main] c.j.s.l.SpringLifecycleApplication       : No active profile set, falling back to default profiles: default
2020-03-02 17:41:23.068  INFO 406860 --- [           main] com.jake.spring.lifecycle.domain.Person  : constructor: Instantiate
2020-03-02 17:41:23.068  INFO 406860 --- [           main] com.jake.spring.lifecycle.domain.Person  : setter: populate property
2020-03-02 17:41:23.069  INFO 406860 --- [           main] com.jake.spring.lifecycle.domain.Person  : setBeanName: singletonPerson
2020-03-02 17:41:23.069  INFO 406860 --- [           main] com.jake.spring.lifecycle.domain.Person  : setBeanFactory
2020-03-02 17:41:23.069  INFO 406860 --- [           main] com.jake.spring.lifecycle.domain.Person  : setApplicationContext
2020-03-02 17:41:23.069  INFO 406860 --- [           main] c.j.s.l.processor.MyBeanPostProcessor    : postProcessBeforeInitialization, beanName = singletonPerson
2020-03-02 17:41:23.069  INFO 406860 --- [           main] com.jake.spring.lifecycle.domain.Person  : afterPropertiesSet
2020-03-02 17:41:23.070  INFO 406860 --- [           main] com.jake.spring.lifecycle.domain.Person  : customInit
2020-03-02 17:41:23.070  INFO 406860 --- [           main] c.j.s.l.processor.MyBeanPostProcessor    : postProcessAfterInitialization, beanName = singletonPerson
2020-03-02 17:41:23.201  INFO 406860 --- [           main] c.j.s.l.SpringLifecycleApplication       : Started SpringLifecycleApplication in 1.921 seconds (JVM running for 3.17)
2020-03-02 17:41:23.209  INFO 406860 --- [extShutdownHook] com.jake.spring.lifecycle.domain.Person  : destroy
2020-03-02 17:41:23.209  INFO 406860 --- [extShutdownHook] com.jake.spring.lifecycle.domain.Person  : customDestroy

打印順序總結如下:

  • 容器啓動後

    • Person#Person() 實例化一個對象
    • Person#setName(String name)設置屬性 name 的值
    • Person#setBeanName(String beanName) 設置 beanName,方法繼承自 BeanNameAware 接口
    • Person#setBeanFactory(BeanFactory beanFactory) 設置 beanFactory,方法繼承自 BeanFactoryAware 接口
    • Person#setApplicationContext(ApplicationContext context) 設置 applicationContext,方法繼承自 ApplicationContextAware 接口
    • MyBeanPostProcessor#postProcessBeforeInitialization(Object bean, String beanName) 初始化前的後置處理,可以猜想後置的含義就是實例化和屬性設置之後。
    • Person#afterPropertiesSet() 此時屬性值已經被設置完畢,方法繼承自 InitializingBean 接口
    • Person#customInit() 自定義初始化方法。
  • 容器關閉後

    • Person#destroy() 銷燬 bean 之前的操作,方法繼承自 DisposableBean 接口
    • Person#customDestroy() 自定義的銷燬 bean 之前的操作

由打印內容 setBeanName: singletonPerson , postProcessBeforeInitialization, beanName = singletonPerson, postProcessAfterInitialization, beanName = singletonPerson 可知,只有 beanName 爲 singletonPerson 的 Bean 纔在 Spring 容器啓動時被初始化。

單元測試

編寫單元測試,注入ApplicationContext 對象,使用工廠方法 getBean(String beanName) 獲取 PersonConfig 中註冊的三種 Bean 實例。

@SpringBootTest
class SpringLifecycleApplicationTests {

    @Autowired
    private ApplicationContext applicationContext;

    @Test
    void getSingletonBean() {
        Person singletonPerson = (Person) applicationContext.getBean("singletonPerson");
        assertNotNull(singletonPerson);
        assertSame("Singleton Jake", singletonPerson.getName());
        assertSame(singletonPerson, applicationContext.getBean("singletonPerson"));
    }

    @Test
    void getLazySingletonBean() {
        Person lazySingletonPerson = (Person) applicationContext.getBean("lazySingletonPerson");
        assertNotNull(lazySingletonPerson);
        assertSame("Lazy Singleton Jake", lazySingletonPerson.getName());
        assertSame(lazySingletonPerson, applicationContext.getBean("lazySingletonPerson"));
    }

    @Test
    void getPrototypeBean() {
        Person prototypePerson = (Person) applicationContext.getBean("prototypePerson");
        assertNotNull(prototypePerson);
        assertSame("Prototype Jake", prototypePerson.getName());
        assertNotSame(prototypePerson, applicationContext.getBean("prototypePerson"));
    }

}

運行單元測試類,在控制檯中輸出結果如下:

  • 容器啓動部分

      .   ____          _            __ _ _
     /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
    ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
     \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
     =========|_|==============|___/=/_/_/_/
     :: Spring Boot ::        (v2.2.4.RELEASE)
    
    2020-03-03 11:42:25.950  INFO 312788 --- [           main] c.j.s.l.SpringLifecycleApplicationTests  : Starting SpringLifecycleApplicationTests on V12CNSSZ01MGRPE with PID 312788 (started by 60055807 in C:\wengzhengkai\idea-projects\spring-lifecycle)
    2020-03-03 11:42:25.952  INFO 312788 --- [           main] c.j.s.l.SpringLifecycleApplicationTests  : No active profile set, falling back to default profiles: default
    2020-03-03 11:42:26.626  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : constructor: Instantiate
    2020-03-03 11:42:26.627  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : setter: populate property
    2020-03-03 11:42:26.639  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : setBeanName: singletonPerson
    2020-03-03 11:42:26.639  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : setBeanFactory
    2020-03-03 11:42:26.639  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : setApplicationContext
    2020-03-03 11:42:26.639  INFO 312788 --- [           main] c.j.s.l.processor.MyBeanPostProcessor    : postProcessBeforeInitialization, beanName = singletonPerson
    2020-03-03 11:42:26.639  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : afterPropertiesSet
    2020-03-03 11:42:26.640  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : customInit
    2020-03-03 11:42:26.640  INFO 312788 --- [           main] c.j.s.l.processor.MyBeanPostProcessor    : postProcessAfterInitialization, beanName = singletonPerson
    2020-03-03 11:42:26.783  INFO 312788 --- [           main] c.j.s.l.SpringLifecycleApplicationTests  : Started SpringLifecycleApplicationTests in 1.259 seconds (JVM running for 3.759)	
    
  • getSingletonBean

    2020-03-03 11:42:27.208  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : getter
    
  • getLazySingletonBean

    2020-03-03 11:42:27.236  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : constructor: Instantiate
    2020-03-03 11:42:27.236  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : setter: populate property
    2020-03-03 11:42:27.236  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : setBeanName: lazySingletonPerson
    2020-03-03 11:42:27.236  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : setBeanFactory
    2020-03-03 11:42:27.236  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : setApplicationContext
    2020-03-03 11:42:27.236  INFO 312788 --- [           main] c.j.s.l.processor.MyBeanPostProcessor    : postProcessBeforeInitialization, beanName = lazySingletonPerson
    2020-03-03 11:42:27.236  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : afterPropertiesSet
    2020-03-03 11:42:27.236  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : customInit
    2020-03-03 11:42:27.237  INFO 312788 --- [           main] c.j.s.l.processor.MyBeanPostProcessor    : postProcessAfterInitialization, beanName = lazySingletonPerson
    2020-03-03 11:42:27.237  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : getter
    
  • getPrototype

    2020-03-03 11:42:27.227  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : constructor: Instantiate
    2020-03-03 11:42:27.227  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : setter: populate property
    2020-03-03 11:42:27.227  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : setBeanName: prototypePerson
    2020-03-03 11:42:27.227  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : setBeanFactory
    2020-03-03 11:42:27.227  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : setApplicationContext
    2020-03-03 11:42:27.228  INFO 312788 --- [           main] c.j.s.l.processor.MyBeanPostProcessor    : postProcessBeforeInitialization, beanName = prototypePerson
    2020-03-03 11:42:27.228  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : afterPropertiesSet
    2020-03-03 11:42:27.228  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : customInit
    2020-03-03 11:42:27.228  INFO 312788 --- [           main] c.j.s.l.processor.MyBeanPostProcessor    : postProcessAfterInitialization, beanName = prototypePerson
    2020-03-03 11:42:27.228  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : getter
    2020-03-03 11:42:27.228  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : constructor: Instantiate
    2020-03-03 11:42:27.228  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : setter: populate property
    2020-03-03 11:42:27.228  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : setBeanName: prototypePerson
    2020-03-03 11:42:27.228  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : setBeanFactory
    2020-03-03 11:42:27.229  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : setApplicationContext
    2020-03-03 11:42:27.229  INFO 312788 --- [           main] c.j.s.l.processor.MyBeanPostProcessor    : postProcessBeforeInitialization, beanName = prototypePerson
    2020-03-03 11:42:27.229  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : afterPropertiesSet
    2020-03-03 11:42:27.229  INFO 312788 --- [           main] com.jake.spring.lifecycle.domain.Person  : customInit
    2020-03-03 11:42:27.229  INFO 312788 --- [           main] c.j.s.l.processor.MyBeanPostProcessor    : postProcessAfterInitialization, beanName = prototypePerson
    

由單元測試的結果可知:

  • 運行 @SpringBootTest 註解的單元測試類和直接運行 Application 在容器啓動部分是一致的,都是默認初始化單例 Bean。
  • @Lazy@Scope("prototype") 註解的 @Bean 配置,都是在被 ApplicationContext#getBean(String beanName) 調用時才進行初始化。
  • @Lazy 註解的 Bean,僅僅做一次初始化,獲取的單例,僅僅是延遲初始化而已。
  • @Scope("prototype") 註解的 Bean,基於原型模式,其背後原理是拷貝,每次都進行初始化。

參考博客

  • Spring Bean 生命週期 (實例結合源碼徹底講透)
  • https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ApplicationContextAware.html
  • https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/InitializingBean.html
  • https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/config/BeanPostProcessor.html
  • https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/DisposableBean.html
  • https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Bean.html
  • https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Configuration.html
  • https://docs.spring.io/spring/docs/3.0.0.M3/reference/html/ch04s04.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章