1. 程式人生 > 其它 >Bean被IoC容器銷燬後還能使用嗎?

Bean被IoC容器銷燬後還能使用嗎?

技術標籤:Spring Contextspringbeaniocjavaspring boot

Talk is cheap. Show me the code

第一步:定義一個Bean用來測試。

package com.xxx.hyl.ioc;

/**
 * Bean 銷燬演示
 * @author 君戰
 *
 * **/
public class DestroyBean {
}

第二步:使用IoC容器來銷燬Bean,再次獲取。

package com.xxx.hyl.ioc;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**** * 演示Bean銷燬後依然可以從IoC容器中獲取 * @author 君戰 * */ public class DestroyBeanDemo { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(DestroyBean.class); context.refresh(); DestroyBean beforeDestroy =
context.getBean(DestroyBean.class); System.out.println("執行Bean銷燬前:" + beforeDestroy); context.getAutowireCapableBeanFactory().destroyBean(beforeDestroy); DestroyBean afterDestroy = context.getBean(DestroyBean.class); System.out.println("執行Bean銷燬後:"
+ afterDestroy); } }

第三步:檢視控制檯列印

需要搞明白的一點是,Bean所實現的或遵守的Spring提供的介面或者按照JSR-330規範的銷燬方法被呼叫時,並不意味著該物件即將被GC掉,這和JVM的垃圾回收機制沒有任何關係,也不意味它會被IoC容器給移除掉。

可以通過手動呼叫AutowireCapableBeanFactory提供的destroyBean方法來觸發指定Bean所實現的銷燬方法的呼叫。該方法需要傳入Bean例項。

/**
	 * Destroy the given bean instance (typically coming from {@link #createBean}),
	 * applying the {@link org.springframework.beans.factory.DisposableBean} contract as well as
	 * registered {@link DestructionAwareBeanPostProcessor DestructionAwareBeanPostProcessors}.
	 * <p>Any exception that arises during destruction should be caught
	 * and logged instead of propagated to the caller of this method.
	 * @param existingBean the bean instance to destroy
	 */
	void destroyBean(Object existingBean);

AbstractAutowireCapableBeanFactory實現了該方法,在該實現中,手動建立DisposableBeanAdapt-er的例項,然後呼叫其destroy方法。其建構函式中需要三個引數,分別為:要銷燬的Bean例項、IoC容器所有已註冊的BeanPostProcessor實現類以及訪問控制上下文。在DisposableBeanAdapter建構函式中通過判斷當前傳入的Bean是否是DisposableBean型別來確定屬性invokeDisposableBean的值。

public void destroyBean(Object existingBean) {
   new DisposableBeanAdapter(existingBean, getBeanPostProcessors(), getAccessControlContext()).destroy();
}

// DisposableBeanAdapter#DisposableBeanAdapter(Object, List<BeanPostProcessor>, AccessControlContext)
public DisposableBeanAdapter(Object bean, List<BeanPostProcessor> postProcessors, AccessControlContext acc) {
		Assert.notNull(bean, "Disposable bean must not be null");
		this.bean = bean;
		this.beanName = bean.getClass().getName();
		this.invokeDisposableBean = (this.bean instanceof DisposableBean);
		this.nonPublicAccessAllowed = true;
		this.acc = acc;
		this.beanPostProcessors = filterPostProcessors(postProcessors, bean);
}

在DisposableBeanAdapter的destroy方法中,首先呼叫IoC容器中所有DestructionAwareBeanPostPr-ocessor介面實現類的postProcessBeforeDestruction方法。Spring IoC容器對於使用JSR-330規範中定義的@PreDestroy註解標註的方法就是在這裡完成的呼叫(CommonAnnotationBeanPostProcessor)。

比較有意思的是接下來處理Bean實現DisposableBean的destroy方法,可以看到使用try…catch捕獲住了該方法可能發生的異常,在catch塊中,僅僅是列印日誌。這也意味著即使在Bean實現Disposable-Bean的destroy方法中發生異常,也不會影響接下來對Bean自定義銷燬方法的呼叫。

最後呼叫Bean自定義的銷燬方法。

// DisposableBeanAdapter#destroy
public void destroy() {
   if (!CollectionUtils.isEmpty(this.beanPostProcessors)) {
      for (DestructionAwareBeanPostProcessor processor : this.beanPostProcessors) {
         processor.postProcessBeforeDestruction(this.bean, this.beanName);
      }
   }

   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自定義的銷燬方法
   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被IoC容器銷燬後還能使用嗎問題的分析,答案是依然可以使用,但不建議使用,因為Bean可能在銷燬方法中釋放了一些資源或者修改了一些狀態,使得Bean不再處於正確的狀態。