以“冬奧之光,多彩冰燈”為主題的第四十一屆全國專業冰雕比賽在冰城哈爾濱市進入第二天,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-method
的bean
的銷燬。
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
容器關閉過程中的。