【spring原始碼系列】之【Bean的銷燬】

小豬爸爸發表於2022-03-08


以“冬奧之光,多彩冰燈”為主題的第四十一屆全國專業冰雕比賽在冰城哈爾濱市進入第二天,60名冰雕高手在哈爾濱冰燈藝術遊園會園區展開激烈的競技比拼。
冰雕藝術

1. 概述

Bean的銷燬是Bean的生命週期中最後一步,比如在Tomcat等容器關閉的時候會呼叫Bean的銷燬方法,下面逐步分析。

2. 原始碼分析

在bean建立完成後,就會對這個bean註冊一個銷燬的Adapter物件,

	protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {

		......

		if (instanceWrapper == null) {
			//建立物件例項
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		Object bean = instanceWrapper.getWrappedInstance();
		Class<?> beanType = instanceWrapper.getWrappedClass();
		if (beanType != NullBean.class) {
			mbd.resolvedTargetType = beanType;
		}

		......

		try {
			// 屬性賦值
			populateBean(beanName, mbd, instanceWrapper);
			// 初始化bean
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		catch (Throwable ex) {
			if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
				throw (BeanCreationException) ex;
			}
			else {
				throw new BeanCreationException(
						mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
			}
		}

		......

		// Register bean as disposable.
		try {
			// 註冊銷燬的bean
			registerDisposableBeanIfNecessary(beanName, bean, mbd);
		}
		catch (BeanDefinitionValidationException ex) {
			throw new BeanCreationException(
					mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
		}

		return exposedObject;
	}

registerDisposableBeanIfNecessary方法中disposableBeans集合負責收集需要銷燬的bean

protected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) {
		AccessControlContext acc = (System.getSecurityManager() != null ? getAccessControlContext() : null);
		if (!mbd.isPrototype() && requiresDestruction(bean, mbd)) {
			if (mbd.isSingleton()) {
				// Register a DisposableBean implementation that performs all destruction
				// work for the given bean: DestructionAwareBeanPostProcessors,
				// DisposableBean interface, custom destroy method.
				// 註冊銷燬bean的DisposableBeanAdapter物件
				registerDisposableBean(beanName,
						new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc));
			}
                ......

public void registerDisposableBean(String beanName, DisposableBean bean) {
		synchronized (this.disposableBeans) {
			this.disposableBeans.put(beanName, bean);
		}
	}

DisposableBeanAdapter 物件就是負責 bean 銷燬的類,這個類中收集 bean是否實現了 DisposableBean 介面

class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable 

是否配置 destroy-method 屬性,過濾了 DestructionAwareBeanPostProcessor 型別的介面,如下圖所示:

public DisposableBeanAdapter(Object bean, String beanName, RootBeanDefinition beanDefinition,
			List<BeanPostProcessor> postProcessors, @Nullable AccessControlContext acc) {

		......

		this.beanPostProcessors = filterPostProcessors(postProcessors, bean);
	}
private List<DestructionAwareBeanPostProcessor> filterPostProcessors(List<BeanPostProcessor> processors, Object bean) {
		List<DestructionAwareBeanPostProcessor> filteredPostProcessors = null;
		if (!CollectionUtils.isEmpty(processors)) {
			filteredPostProcessors = new ArrayList<>(processors.size());
			for (BeanPostProcessor processor : processors) {
				if (processor instanceof DestructionAwareBeanPostProcessor) {
					DestructionAwareBeanPostProcessor dabpp = (DestructionAwareBeanPostProcessor) processor;
					if (dabpp.requiresDestruction(bean)) {
						filteredPostProcessors.add(dabpp);
					}
				}
			}
		}
		return filteredPostProcessors;

然後 bean 是在什麼時候被銷燬呢,在 tomcat 關閉的時候就會呼叫到 servlet 中的銷燬方法,具體是通過類ContextLoaderListener.java 中的contextDestroyed 方法,通過 closeWebApplicationContext 方法一直往下找此為 servlet 規範的使用,一路往下呼叫。








最終會進入DisposableBeanAdapter類中的destroy,方法該方法就會根據前面的收集進行呼叫。

public void destroy() {
		// 處理@PreDestroy註解的beanpostProcessor實現類: InitDestroyAnnotationBeanPostProcessor
		if (!CollectionUtils.isEmpty(this.beanPostProcessors)) {
			for (DestructionAwareBeanPostProcessor processor : this.beanPostProcessors) {
				processor.postProcessBeforeDestruction(this.bean, this.beanName);
			}
		}
		// 處理實現DisposableBean介面的bean的銷燬
		if (this.invokeDisposableBean) {
			if (logger.isTraceEnabled()) {
				logger.trace("Invoking destroy() on bean with name '" + this.beanName + "'");
			}
			try {
				if (System.getSecurityManager() != null) {
					AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
						((DisposableBean) this.bean).destroy();
						return null;
					}, this.acc);
				}
				else {
					((DisposableBean) this.bean).destroy();
				}
			}
			catch (Throwable ex) {
				String msg = "Invocation of destroy method failed on bean with name '" + this.beanName + "'";
				if (logger.isDebugEnabled()) {
					logger.warn(msg, ex);
				}
				else {
					logger.warn(msg + ": " + ex);
				}
			}
		}

		// 處理在配置檔案中的bean配置了destroy-method的bean的銷燬
		if (this.destroyMethod != null) {
			invokeCustomDestroyMethod(this.destroyMethod);
		}
		else if (this.destroyMethodName != null) {
			Method methodToInvoke = determineDestroyMethod(this.destroyMethodName);
			if (methodToInvoke != null) {
				invokeCustomDestroyMethod(ClassUtils.getInterfaceMethodIfPossible(methodToInvoke));
			}
		}
	}

銷燬bean的順序是如下:
1)判斷是否需要處理@PreDestroy註解的bean,如果需要,則通過beanpostProcessor實現類 InitDestroyAnnotationBeanPostProcessor處理;
2)判斷是否需要處理實現DisposableBean介面的bean的銷燬;
3)判斷是否需要處理配置檔案中的bean配置了destroy-methodbean的銷燬。

3. 案例演示

定義Bean,同時加入銷燬對應的三種方法;

/**
 * @Author: wzj
 * @Date: 2021/7/2 11:32
 * @Desc:
 */
public class Wzj implements DisposableBean {
    
    @PreDestroy
    public void close() {
        System.out.println("通過 @PreDestroy:銷燬例項wzj");
    }

    public void destroyMethod() {
        System.out.println("通過配置檔案配置destroy-method:銷燬例項wzj");
    }

    @Override
    public void destroy() {
        System.out.println("通過DisposableBean介面:銷燬例項wzj");
    }

配置檔案如下:

	<bean id="wzj"  class="com.wzj.bean.Wzj" factory-method="factoryMethod" destroy-method="destroyMethod"/>

測試類:

/**
 * @Author: wzj
 * @Date: 2021/3/30 15:08
 * @Desc: 測試類
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring.xml"})
public class TestSpring {

    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void testDestroy() {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Wzj wzj = (Wzj)applicationContext.getBean("wzj");
        applicationContext.getBeanFactory().destroyBean("wzj");
  
    }

結果:

有人可能會問,為何Bean可以多次銷燬,其實Bean的銷燬並不是真正意義上的銷燬Bean,而是在銷燬前執行銷燬方法,可能包含關閉資料庫連線、關閉網路請求等邏輯操作,而後真正的銷燬是由Spring容器執行關閉,其內部Bean也就自然而然消失了,Bean銷燬是發生在Spring容器關閉過程中的。

相關文章