本文是自己學習的一個總結,和該文章對應的是bean的初始化這篇文章,鏈接如下https://blog.csdn.net/sinat_38393872/article/details/106996679
1、bean的銷燬簡介
1.1、bean的初始化發生在什麼階段
Bean的銷燬一般發生在容器關閉的階段。我們可以在銷燬時定製一些動作滿足需求
2、Bean銷燬的回調函數
2.1、基於@PreDestroy,銷燬前回調函數
2.1.1、使用@PreDestroy
@PreDestroy,從名字上看就能知道,是bean銷燬前的回調函數。該註解的使用方式是在被定義成Bean的類A中實現一個方法,並用@PreDestroy標註這個方法,那麼這個方法就是類A作爲Bean在銷燬前的回調方法。類A作爲bean在銷燬前,會調用被@PreDestroy標註的方法。
我們看看例子。
DefaultUserFactory的實現如下,這是我們要註冊爲Bean的類。
@Component
public class DefaultUserFactory {
@PreDestroy
public void preDestroy() {
System.out.println("@PreDestroy:DefaultUserFactory銷燬中");
}
}
生成容器,並註冊DefaultUserFactory,隨後立即銷燬,我們看系統在DefaultUserFactory銷燬前會不會調用preDestroy()函數。
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(DefaultUserFactory.class);
applicationContext.refresh();
applicationContext.close();
最後打印結果如下,回調函數成功調用。
2.1.2、同一個類中使用多個@PreDestroy
和bean初始化的@PostConstruct一樣,@PreDestroy在類中使用標註方法,當這個類被初始化成bean之後就會調用@PreDestroy標註的方法。
其中,@PreDestroy可以在類中標註多個方法,並且類被初始化成bean之後,所有被@PreDestroy標註的方法都會被回調,但是調用的順序不能保證,並不是按定義順序調用的,系統似乎有自己的一套規則。
2.1.3、註解生效範圍
@PreDestroy在類A 中使用,那只有類A是通過註解的方式初始化成bean時,@PreDestroy纔會生效。
但是如果類A是通過XML初始化成bean,那@PreDestroy就不會起作用。
2.2、實現DisposableBean覆寫destroy
若類A要被註冊爲bean,那可令類A實現DisposableBean接口,覆寫其中的destroy方法。這樣A作爲bean在銷燬過程中會回調覆寫的destroy方法。
基於上面的代碼,我們在DefaultUserFactory中加入destroy相關的代碼
@Component
public class DefaultUserFactory implements DisposableBean {
@PreDestroy
public void preDestroy() {
System.out.println("@PreDestroy:DefaultUserFactory銷燬中");
}
@Override
public void destroy() throws Exception {
System.out.println("destroy:DefaultUserFactory銷燬中");
}
}
其餘代碼不變
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(DefaultUserFactory.class);
applicationContext.refresh();
applicationContext.close();
最後輸出結果如下
### 2.2.1、destroy的生效範圍 實現DisposableBean覆寫destroy,這個方法與@PreDestroy不同,無論是通過註解還是通過xml,destroy都可以生效。
## 2.3、基於@Bean的destroyMethod屬性,初始化後回調函數 @Bean中有個屬性是destroyMethod,他是用來將類中的某個方法指定爲銷燬函數。我們看看例子。
接着上面的代碼,我們在DefaultUserFactory中加入@Bean相關的代碼
@Component
public class DefaultUserFactory implements DisposableBean {
@PreDestroy
public void preDestroy() {
System.out.println("@PreDestroy:DefaultUserFactory銷燬中");
}
@Override
public void destroy() throws Exception {
System.out.println("destroy:DefaultUserFactory銷燬中");
}
@Bean(destroyMethod = "doDestroy")
public DefaultUserFactory getDefaultUserFactory() {
return new DefaultUserFactory();
}
public void doDestroy() {
System.out.println("@Bean.destroyMethod:DefaultUserFactory銷燬中");
}
}
其餘代碼不變,
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(DefaultUserFactory.class);
applicationContext.refresh();
applicationContext.close();
可以預測,最後生成的容器中有兩個類型爲DefaultUserFactory的Bean。一個是容器生成時註冊進去的bean,這個bean只設置了@PreDestroy和destroy的回調函數;另一個是基於@Bean生成的bean,這個類不僅有@PreDestroy和destroy的回調函數,還有destroyMethod的回調函數。
最後輸出結果如下
2.3.1、destroyMethod的生效範圍
因爲這也是基於註解實現的,所以只有容器是通過註解生成時destroyMethod纔有效,通過xml生成容器時destroyMethod會無效。
2.4、@PreDestroy,destroy和destroyMethod的執行順序
bean的銷燬和bean的初始化很像,兩者的實現方式也都是註解,實現接口,@Bean屬性三種方式。而這三種實現方式的執行順序在bean的銷燬和初始化的執行順序也一樣。
bean的初始化又分爲構造->屬性填充->初始化,而以上三種方式就對應着這三個階段。
@PostConstruct在構造結束後會被調用,afterPropertiesSet在屬性填充後會被調用,initMethod在初始化完成後會被調用。
所以執行順序是@PreDestroy -> destroy -> destroyMethod
3、java GC 什麼時候回收spring容器管理的bean對象
在Spring容器關閉了以後,Java GC會對容器中的bean進行回收。當然,就GC的特性而言,基本上不可能容器一關閉其中的bean對象就會被回收,GC的處理是隨機的。
我們可以用下面的代碼來驗證這一觀點。我們繼續沿用上面的代碼,然後令DefaultUserFactory複寫finalize方法。
@Override
protected void finalize() throws Throwable {
System.out.println("bean對象正在被回收");
}
之後在容器關閉以後強制執行GC操作,並且GC操作以後沉睡一會。之所以要沉睡一會是因爲GC的操作是隨機的,不是立刻執行的,爲了防止GC還沒運行程序就直接退出看不到效果纔在GC回收語句之後沉睡一會。
public static void main(String[] args) throws Exception{
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(DefaultUserFactory.class);
applicationContext.refresh();
applicationContext.close();
System.gc();
Thread.sleep(50000L);
}
輸出結果如下
我們可以看到,Spring容器中的bean對象是可以被GC回收的,並且回收是在容器關閉以後。