【Spring註解驅動開發】使用InitializingBean和DisposableBean來管理bean的生命週期,你真的瞭解嗎?

冰河團隊發表於2020-06-21

寫在前面

在《【Spring註解驅動開發】如何使用@Bean註解指定初始化和銷燬的方法?看這一篇就夠了!!》一文中,我們講述瞭如何使用@Bean註解來指定bean初始化和銷燬的方法。具體的用法就是在@Bean註解中使用init-method屬性和destroy-method屬性來指定初始化方法和銷燬方法。除此之外,Spring中是否還提供了其他的方式來對bean例項進行初始化和銷燬呢?

專案工程原始碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

InitializingBean介面

1.InitializingBean介面概述

Spring中提供了一個InitializingBean介面,InitializingBean介面為bean提供了屬性初始化後的處理方法,它只包括afterPropertiesSet方法,凡是繼承該介面的類,在bean的屬性初始化後都會執行該方法。InitializingBean介面的原始碼如下所示。

package org.springframework.beans.factory;
public interface InitializingBean {
	void afterPropertiesSet() throws Exception;
}

根據InitializingBean介面中提供的afterPropertiesSet()方法的名字可以推斷出:afterPropertiesSet()方法是在屬性賦好值之後呼叫的。那到底是不是這樣呢?我們來分析下afterPropertiesSet()方法的呼叫時機。

2.何時呼叫InitializingBean介面?

我們定位到Spring中的org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory類下的invokeInitMethods()方法中,來檢視Spring載入bean的方法。

題外話:不要問我為什麼會是這個invokeInitMethods()方法,如果你和我一樣對Spring的原始碼非常熟悉的話,你也會知道是這個invokeInitMethods()方法,哈哈哈哈!所以,小夥伴們不要只顧著使用Spring,還是要多看看Spring的原始碼啊!Spring框架中使用了大量優秀的設計模型,其程式碼的編寫規範和嚴謹程度也是業界開源框架中數一數二的,非常值得閱讀。

我們來到AbstractAutowireCapableBeanFactory類下的invokeInitMethods()方法,如下所示。

protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
    throws Throwable {
	//判斷該bean是否實現了實現了InitializingBean介面,如果實現了InitializingBean介面,則呼叫bean的afterPropertiesSet方法
    boolean isInitializingBean = (bean instanceof InitializingBean);
    if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
        if (logger.isTraceEnabled()) {
            logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
        }
        if (System.getSecurityManager() != null) {
            try {
                AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
                    //呼叫afterPropertiesSet()方法
                    ((InitializingBean) bean).afterPropertiesSet();
                    return null;
                }, getAccessControlContext());
            }
            catch (PrivilegedActionException pae) {
                throw pae.getException();
            }
        }
        else {
            //呼叫afterPropertiesSet()方法
            ((InitializingBean) bean).afterPropertiesSet();
        }
    }

    if (mbd != null && bean.getClass() != NullBean.class) {
        String initMethodName = mbd.getInitMethodName();
        if (StringUtils.hasLength(initMethodName) &&
            !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
            !mbd.isExternallyManagedInitMethod(initMethodName)) {
            //通過反射的方式呼叫init-method
            invokeCustomInitMethod(beanName, bean, mbd);
        }
    }
}

分析上述程式碼後,我們可以初步得出如下資訊:

  • Spring為bean提供了兩種初始化bean的方式,實現InitializingBean介面,實現afterPropertiesSet方法,或者在配置檔案和@Bean註解中通過init-method指定,兩種方式可以同時使用。
  • 實現InitializingBean介面是直接呼叫afterPropertiesSet()方法,比通過反射呼叫init-method指定的方法效率相對來說要高點。但是init-method方式消除了對Spring的依賴。
  • 如果呼叫afterPropertiesSet方法時出錯,則不呼叫init-method指定的方法。

也就是說Spring為bean提供了兩種初始化的方式,第一種實現InitializingBean介面,實現afterPropertiesSet方法,第二種配置檔案或@Bean註解中通過init-method指定,兩種方式可以同時使用,同時使用先呼叫afterPropertiesSet方法,後執行init-method指定的方法。

DisposableBean介面

1.DisposableBean介面概述

實現org.springframework.beans.factory.DisposableBean介面的bean在銷燬前,Spring將會呼叫DisposableBean介面的destroy()方法。我們先來看下DisposableBean介面的原始碼,如下所示。

package org.springframework.beans.factory;
public interface DisposableBean {
	void destroy() throws Exception;
}

可以看到,在DisposableBean介面中只定義了一個destroy()方法。

在Bean生命週期結束前呼叫destory()方法做一些收尾工作,亦可以使用destory-method。前者與Spring耦合高,使用型別強轉.方法名(),效率高。後者耦合低,使用反射,效率相對低

2.DisposableBean介面注意事項

多例bean的生命週期不歸Spring容器來管理,這裡的DisposableBean中的方法是由Spring容器來呼叫的,所以如果一個多例實現了DisposableBean是沒有啥意義的,因為相應的方法根本不會被呼叫,當然在XML配置檔案中指定了destroy方法,也是沒有意義的。所以,在多例項bean情況下,Spring不會自動呼叫bean的銷燬方法。

單例項bean案例

建立一個Animal的類實現InitializingBean和DisposableBean介面,程式碼如下:

package io.mykit.spring.plugins.register.bean;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
/**
 * @author binghe
 * @version 1.0.0
 * @description 測試InitializingBean介面和DisposableBean介面
 */
public class Animal implements InitializingBean, DisposableBean {
    public Animal(){
        System.out.println("執行了Animal類的無引數構造方法");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("執行了Animal類的初始化方法。。。。。");

    }
    @Override
    public void destroy() throws Exception {
        System.out.println("執行了Animal類的銷燬方法。。。。。");

    }
}

接下來,我們新建一個AnimalConfig類,並將Animal通過@Bean註解的方式註冊到Spring容器中,如下所示。

package io.mykit.spring.plugins.register.config;

import io.mykit.spring.plugins.register.bean.Animal;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
 * @author binghe
 * @version 1.0.0
 * @description AnimalConfig
 */
@Configuration
@ComponentScan("io.mykit.spring.plugins.register.bean")
public class AnimalConfig {
    @Bean
    public Animal animal(){
        return new Animal();
    }
}

接下來,我們在BeanLifeCircleTest類中新增testBeanLifeCircle02()方法來進行測試,如下所示。

@Test
public void testBeanLifeCircle02(){
    //建立IOC容器
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnimalConfig.class);
    System.out.println("IOC容器建立完成...");
    //關閉IOC容器
    context.close();
}

執行BeanLifeCircleTest類中的testBeanLifeCircle02()方法,輸出的結果資訊如下所示。

執行了Animal類的無引數構造方法
執行了Animal類的初始化方法。。。。。
IOC容器建立完成...
執行了Animal類的銷燬方法。。。。。

從輸出的結果資訊可以看出:單例項bean下,IOC容器建立完成後,會自動呼叫bean的初始化方法;而在容器銷燬前,會自動呼叫bean的銷燬方法。

多例項bean案例

多例項bean的案例程式碼基本與單例項bean的案例程式碼相同,只不過在AnimalConfig類中,我們在animal()方法上新增了@Scope("prototype")註解,如下所示。

package io.mykit.spring.plugins.register.config;
import io.mykit.spring.plugins.register.bean.Animal;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
/**
 * @author binghe
 * @version 1.0.0
 * @description AnimalConfig
 */
@Configuration
@ComponentScan("io.mykit.spring.plugins.register.bean")
public class AnimalConfig {
    @Bean
    @Scope("prototype")
    public Animal animal(){
        return new Animal();
    }
}

接下來,我們在BeanLifeCircleTest類中新增testBeanLifeCircle03()方法來進行測試,如下所示。

@Test
public void testBeanLifeCircle03(){
    //建立IOC容器
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AnimalConfig.class);
    System.out.println("IOC容器建立完成...");
    System.out.println("-------");
    //呼叫時建立物件
    Object bean = ctx.getBean("animal");
    System.out.println("-------");
    //呼叫時建立物件
    Object bean1 = ctx.getBean("animal");
    System.out.println("-------");
    //關閉IOC容器
    ctx.close();
}

執行BeanLifeCircleTest類中的testBeanLifeCircle03()方法,輸出的結果資訊如下所示。

IOC容器建立完成...
-------
執行了Animal類的無引數構造方法
執行了Animal類的初始化方法。。。。。
-------
執行了Animal類的無引數構造方法
執行了Animal類的初始化方法。。。。。
-------

從輸出的結果資訊中可以看出:在多例項bean情況下,Spring不會自動呼叫bean的銷燬方法。

好了,我們們今天就聊到這兒吧!別忘了給個在看和轉發,讓更多的人看到,一起學習一起進步!!

專案工程原始碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

寫在最後

如果覺得文章對你有點幫助,請微信搜尋並關注「 冰河技術 」微信公眾號,跟冰河學習Spring註解驅動開發。公眾號回覆“spring註解”關鍵字,領取Spring註解驅動開發核心知識圖,讓Spring註解驅動開發不再迷茫。

相關文章