淺析spring——IOC 之 分析 Bean 的生命週期

Java_蘇先生發表於2019-04-28

在分析 Spring Bean 例項化過程中提到 Spring 並不是一啟動容器就開啟 bean 的例項化程式,只有當客戶端通過顯示或者隱式的方式呼叫 BeanFactory 的 getBean() 方法來請求某個例項物件的時候,它才會觸發相應 bean 的例項化程式,當然也可以選擇直接使用 ApplicationContext 容器,因為該容器啟動的時候會立刻呼叫註冊到該容器所有 bean 定義的例項化方法。當然對於 BeanFactory 容器而言並不是所有的 getBean() 方法都會觸發例項化程式,比如 signleton 型別的 bean,該型別的 bean 只會在第一次呼叫 getBean() 的時候才會觸發,而後續的呼叫則會直接返回容器快取中的例項物件。

getBean() 只是 bean 例項化程式的入口,真正的實現邏輯其實是在 AbstractAutowireCapableBeanFactory 的 doCreateBean() 實現,例項化過程如下圖:

淺析spring——IOC 之 分析 Bean 的生命週期

原來我們採用 new 的方式建立一個物件,用完該物件在其脫離作用域後就會被回收,對於後續操作我們無權也沒法干涉,但是採用 Spring 容器後,我們完全擺脫了這種命運,Spring 容器將會對其所有管理的 Bean 物件全部給予一個統一的生命週期管理,同時在這個階段我們也可以對其進行干涉(比如對 bean 進行增強處理,對 bean 進行篡改),如上圖。

bean 例項化


在 doCreateBean() 中首先進行 bean 例項化工作,主要由 createBeanInstance() 實現,該方法返回一個 BeanWrapper 物件。BeanWrapper 物件是 Spring 的一個低階 Bean 基礎結構的核心介面,為什麼說是低階呢?因為這個時候的 Bean 還不能夠被我們使用,連最基本的屬性都沒有設定。而且在我們實際開發過程中一般都不會直接使用該類,而是通過 BeanFactory 隱式使用。

BeanWrapper 介面有一個預設實現類 BeanWrapperImpl,其主要作用是對 Bean 進行“包裹”,然後對這個包裹的 bean 進行操作,比如後續注入 bean 屬性。

在例項化 bean 過程中,Spring 採用“策略模式”來決定採用哪種方式來例項化 bean,一般有反射和 CGLIB 動態位元組碼兩種方式。

InstantiationStrategy 定義了 Bean 例項化策略的抽象介面,其子類 SimpleInstantiationStrategy 提供了基於反射來例項化物件的功能,但是不支援方法注入方式的物件例項化。CglibSubclassingInstantiationStrategy 繼承 SimpleInstantiationStrategy,他除了擁有父類以反射例項化物件的功能外,還提供了通過 CGLIB 的動態位元組碼的功能進而支援方法注入所需的物件例項化需求。預設情況下,Spring 採用 CglibSubclassingInstantiationStrategy。

啟用 Aware


當 Spring 完成 bean 物件例項化並且設定完相關屬性和依賴後,則會開始 bean 的初始化程式(initializeBean()),初始化第一個階段是檢查當前 bean 物件是否實現了一系列以 Aware 結尾的的介面。

Aware 介面為 Spring 容器的核心介面,是一個具有標識作用的超級介面,實現了該介面的 bean 是具有被 Spring 容器通知的能力,通知的方式是採用回撥的方式。

在初始化階段主要是感知 BeanNameAware、BeanClassLoaderAware、BeanFactoryAware :

private void invokeAwareMethods(final String beanName, final Object bean) {
	if (bean instanceof Aware) {
		if (bean instanceof BeanNameAware) {
			((BeanNameAware) bean).setBeanName(beanName);
		}
		if (bean instanceof BeanClassLoaderAware) {
			ClassLoader bcl = getBeanClassLoader();
			if (bcl != null) {
				((BeanClassLoaderAware) bean).setBeanClassLoader(bcl);
			}
		}
		if (bean instanceof BeanFactoryAware) {
			((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
		}
	}
}
複製程式碼

BeanNameAware:對該 bean 物件定義的 beanName 設定到當前物件例項中 BeanClassLoaderAware:將當前 bean 物件相應的 ClassLoader 注入到當前物件例項中 BeanFactoryAware:BeanFactory 容器會將自身注入到當前物件例項中,這樣當前物件就會擁有一個 BeanFactory 容器的引用。 當然,Spring 不僅僅只是提供了上面三個 Aware 介面,而是一系列:

  • LoadTimeWeaverAware:載入Spring Bean時織入第三方模組,如AspectJ
  • BootstrapContextAware:資源介面卡BootstrapContext,如JCA,CCI
  • ResourceLoaderAware:底層訪問資源的載入器
  • PortletConfigAware:PortletConfig
  • PortletContextAware:PortletContext
  • ServletConfigAware:ServletConfig
  • ServletContextAware:ServletContext
  • MessageSourceAware:國際化
  • ApplicationEventPublisherAware:應用事件
  • NotificationPublisherAware:JMX通知

BeanPostProcessor


初始化第二個階段則是 BeanPostProcessor 增強處理,在該階段 BeanPostProcessor 會處理當前容器內所有符合條件的例項化後的 bean 物件。它主要是對 Spring 容器提供的 bean 例項物件進行有效的擴充套件,允許 Spring 在初始化 bean 階段對其進行定製化修改,如處理標記介面或者為其提供代理實現。

BeanPostProcessor 介面提供了兩個方法,在不同的時機執行,分別對應上圖的前置處理和後置處理。

public interface BeanPostProcessor {
	@Nullable
	 default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}
	@Nullable
	 default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}
}
複製程式碼

InitializingBean 和 init-method


InitializingBean 是一個介面,它為 Spring Bean 的初始化提供了一種方式,它有一個 afterPropertiesSet() 方法,在 bean 的初始化程式中會判斷當前 bean 是否實現了 InitializingBean,如果實現了則呼叫 afterPropertiesSet() 進行初始化工作。然後再檢查是否也指定了 init-method(),如果指定了則通過反射機制呼叫指定的 init-method()。

protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
   throws Throwable {
	Boolean isInitializingBean = (bean instanceof InitializingBean);
	if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
		if (logger.isDebugEnabled()) {
			logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
		}
		if (System.getSecurityManager() != null) {
			try {
				AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
					((InitializingBean) bean).afterPropertiesSet();
					return null;
				}
				, getAccessControlContext());
			}
			catch (PrivilegedActionException pae) {
				throw pae.getException();
			}
		} else {
			((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)) {
			invokeCustomInitMethod(beanName, bean, mbd);
		}
	}
}
複製程式碼

對於 Spring 而言,雖然上面兩種方式都可以實現初始化定製化,但是更加推崇 init-method 方式,因為對於 InitializingBean 介面而言,他需要 bean 去實現介面,這樣就會汙染我們的應用程式,顯得 Spring 具有一定的侵入性。但是由於 init-method 是採用反射的方式,所以執行效率上相對於 InitializingBean 介面回撥的方式可能會低一些。

DisposableBean 和 destroy-method


與 InitializingBean 和 init-method 用於物件的自定義初始化工作相似,DisposableBean和 destroy-method 則用於物件的自定義銷燬工作。

當一個 bean 物件經歷了例項化、設定屬性、初始化階段,那麼該 bean 物件就可以供容器使用了(呼叫的過程)。當完成呼叫後,如果是 singleton 型別的 bean ,則會看當前 bean 是否應實現了 DisposableBean 介面或者配置了 destroy-method 屬性,如果是的話,則會為該例項註冊一個用於物件銷燬的回撥方法,便於在這些 singleton 型別的 bean 物件銷燬之前執行銷燬邏輯。

但是,並不是物件完成呼叫後就會立刻執行銷燬方法,因為這個時候 Spring 容器還處於執行階段,只有當 Spring 容器關閉的時候才會去呼叫。但是, Spring 容器不會這麼聰明會自動去呼叫這些銷燬方法,而是需要我們主動去告知 Spring 容器。

  • 對於 BeanFactory 容器而言,我們需要主動呼叫 destroySingletons() 通知 BeanFactory 容器去執行相應的銷燬方法。
  • 對於 ApplicationContext 容器而言呼叫 registerShutdownHook() 方法。

實踐驗證


下面用一個例項來真實看看看上面執行的邏輯,畢竟理論是不能缺少實踐的:

public class lifeCycleBean implements BeanNameAware,BeanFactoryAware,BeanClassLoaderAware,BeanPostProcessor,
        InitializingBean,DisposableBean {

    private String test;

    public String getTest() {
        return test;
    }

    public void setTest(String test) {
        System.out.println("屬性注入....");
        this.test = test;
    }

    public lifeCycleBean(){
        System.out.println("建構函式呼叫...");
    }

    public void display(){
        System.out.println("方法呼叫...");
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("BeanFactoryAware 被呼叫...");
    }

    @Override
    public void setBeanName(String name) {
        System.out.println("BeanNameAware 被呼叫...");
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        System.out.println("BeanClassLoaderAware 被呼叫...");
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("BeanPostProcessor postProcessBeforeInitialization 被呼叫...");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("BeanPostProcessor postProcessAfterInitialization 被呼叫...");
        return bean;
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("DisposableBean destroy 被調動...");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean afterPropertiesSet 被調動...");
    }

    public void initMethod(){
        System.out.println("init-method 被呼叫...");
    }

    public void destroyMethdo(){
        System.out.println("destroy-method 被呼叫...");
    }

}
複製程式碼

lifeCycleBean 繼承了 BeanNameAware , BeanFactoryAware , BeanClassLoaderAware , BeanPostProcessor , InitializingBean , DisposableBean 六個介面,同時定義了一個 test 屬性用於驗證屬性注入和提供一個 display() 用於模擬呼叫。 配置如下:

<bean id="lifeCycle" class="org.springframework.core.test.lifeCycleBean"
        init-method="initMethod" destroy-method="destroyMethdo">
    <property name="test" value="test"/>
</bean>
複製程式碼

配置 init-method 和 destroy-method。測試方法如下:

// BeanFactory 容器一定要呼叫該方法進行 BeanPostProcessor 註冊
factory.addBeanPostProcessor(new lifeCycleBean());

lifeCycleBean lifeCycleBean = (lifeCycleBean) factory.getBean("lifeCycle");
lifeCycleBean.display();

System.out.println("方法呼叫完成,容器開始關閉....");
// 關閉容器
factory.destroySingletons();
複製程式碼

執行結果:

建構函式呼叫...
建構函式呼叫...
屬性注入....
BeanNameAware 被呼叫...
BeanClassLoaderAware 被呼叫...
BeanFactoryAware 被呼叫...
BeanPostProcessor postProcessBeforeInitialization 被呼叫...
InitializingBean afterPropertiesSet 被調動...
init-method 被呼叫...
BeanPostProcessor postProcessAfterInitialization 被呼叫...
方法呼叫...
方法呼叫完成,容器開始關閉....
DisposableBean destroy 被調動...
destroy-method 被呼叫...
複製程式碼

有兩個建構函式呼叫是因為要注入一個 BeanPostProcessor(你也可以另外提供一個 BeanPostProcessor 例項)。

根據執行的結果已經上面的分析,我們就可以對 Spring Bean 的宣告週期過程如下(方法級別)

  • Spring 容器根據例項化策略對 Bean 進行例項化。
  • 例項化完成後,如果該 bean 設定了一些屬性的話,則利用 set 方法設定一些屬性。
  • 如果該 Bean 實現了 BeanNameAware 介面,則呼叫 setBeanName() 方法。
  • 如果該 bean 實現了 BeanClassLoaderAware 介面,則呼叫 setBeanClassLoader() 方法。
  • 如果該 bean 實現了 BeanFactoryAware介面,則呼叫 setBeanFactory() 方法。
  • 如果該容器註冊了 BeanPostProcessor,則會呼叫postProcessBeforeInitialization() 方法完成 bean 前置處理
  • 如果該 bean 實現了 InitializingBean 介面,則呼叫 。afterPropertiesSet() 方法。
  • 如果該 bean 配置了 init-method 方法,則呼叫 init-method 指定的方法。
  • 初始化完成後,如果該容器註冊了 BeanPostProcessor 則會呼叫 postProcessAfterInitialization() 方法完成 bean 的後置處理。
  • 物件完成初始化,開始方法呼叫。
  • 在容器進行關閉之前,如果該 bean 實現了 DisposableBean 介面,則呼叫 destroy() 方法。
  • 在容器進行關閉之前,如果該 bean 配置了 destroy-mehod,則呼叫其指定的方法。
  • 到這裡一個 bean 也就完成了它的一生。

相關文章