🙈羞,Spring Bean 初始化/銷燬竟然有這麼多姿勢
文章來源:http://1t.click/bfHN
一、前言
日常開發過程有時需要在應用啟動之後載入某些資源,或者在應用關閉之前釋放資源。Spring 框架提供相關功能,圍繞 Spring Bean
生命週期,可以在 Bean
建立過程初始化資源,以及銷燬 Bean
過程釋放資源。Spring 提供多種不同的方式初始化/銷燬 Bean
,如果同時使用這幾種方式,Spring 如何處理這幾者之間的順序?
有沒有覺得標題很熟悉,沒錯標題模仿二哥 「@沉默王二」 文章羞,Java 字串拼接竟然有這麼多姿勢。
二、姿勢剖析
首先我們先來回顧一下 Spring 初始化/銷燬 Bean
幾種方式,分別為:
init-method/destroy-method
InitializingBean/DisposableBean
@PostConstruct/@PreDestroy
ContextStartedEvent/ContextClosedEvent
PS: 其實還有一種方式,就是繼承 Spring
Lifecycle
介面。不過這種方式比較繁瑣,這裡就不再分析。
2.1、init-method/destroy-method
這種方式在配置檔案檔案指定初始化/銷燬方法。XML 配置如下
<bean id="demoService" class="com.dubbo.example.provider.DemoServiceImpl" destroy-method="close" init-method="initMethod"/>
或者也可以使用註解方式配置:
@Configurable
public class AppConfig {
@Bean(initMethod = "init", destroyMethod = "destroy")
public HelloService hello() {
return new HelloService();
}
}
還記得剛開始接觸學習 Spring 框架,使用就是這種方式。
2.2、InitializingBean/DisposableBean
這種方式需要繼承 Spring 介面 InitializingBean/DisposableBean
InitializingBean
用於初始化動作,而 DisposableBean
用於銷燬之前清理動作。使用方式如下:
@Service
public class HelloService implements InitializingBean, DisposableBean {
@Override
public void destroy() throws Exception {
System.out.println("hello destroy...");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("hello init....");
}
}
2.3、@PostConstruct/@PreDestroy
這種方式相對於上面兩種方式來說,使用方式最簡單,只需要在相應的方法上使用註解即可。使用方式如下:
@Service
public class HelloService {
@PostConstruct
public void init() {
System.out.println("hello @PostConstruct");
}
@PreDestroy
public void PreDestroy() {
System.out.println("hello @PreDestroy");
}
}
這裡踩過一個坑,如果使用 JDK9 之後版本 ,
@PostConstruct/@PreDestroy
需要使用 maven 單獨引入javax.annotation-api
,否者註解不會生效。
2.4、ContextStartedEvent/ContextClosedEvent
這種方式使用 Spring 事件機制,日常業務開發比較少見,常用與框架整合中。Spring 啟動之後將會發送 ContextStartedEvent
事件,而關閉之前將會發送 ContextClosedEvent
事件。我們需要繼承 Spring ApplicationListener
才能監聽以上兩種事件。
@Service
public class HelloListener implements ApplicationListener {
@Override
public void onApplicationEvent(ApplicationEvent event) {
if(event instanceof ContextClosedEvent){
System.out.println("hello ContextClosedEvent");
}else if(event instanceof ContextStartedEvent){
System.out.println("hello ContextStartedEvent");
}
}
}
也可以使用 @EventListener
註解,使用方式如下:
public class HelloListenerV2 {
@EventListener(value = {ContextClosedEvent.class, ContextStartedEvent.class})
public void receiveEvents(ApplicationEvent event) {
if (event instanceof ContextClosedEvent) {
System.out.println("hello ContextClosedEvent");
} else if (event instanceof ContextStartedEvent) {
System.out.println("hello ContextStartedEvent");
}
}
}
PS:只有呼叫
ApplicationContext#start
才會傳送ContextStartedEvent
。若不想這麼麻煩,可以監聽ContextRefreshedEvent
事件代替。一旦 Spring 容器初始化完成,就會發送ContextRefreshedEvent
。
三、綜合使用
回顧完上面幾種方式,這裡我們綜合使用上面的四種方式,來看下 Spring 內部的處理順序。在看結果之前,各位讀者大人可以猜測下這幾種方式的執行順序。
public class HelloService implements InitializingBean, DisposableBean {
@PostConstruct
public void init() {
System.out.println("hello @PostConstruct");
}
@PreDestroy
public void PreDestroy() {
System.out.println("hello @PreDestroy");
}
@Override
public void destroy() throws Exception {
System.out.println("bye DisposableBean...");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("hello InitializingBean....");
}
public void xmlinit(){
System.out.println("hello xml-init...");
}
public void xmlDestory(){
System.out.println("bye xmlDestory...");
}
@EventListener(value = {ContextClosedEvent.class, ContextStartedEvent.class})
public void receiveEvents(ApplicationEvent event) {
if (event instanceof ContextClosedEvent) {
System.out.println("bye ContextClosedEvent");
} else if (event instanceof ContextStartedEvent) {
System.out.println("hello ContextStartedEvent");
}
}
}
xml 配置方式如下:
<context:annotation-config />
<context:component-scan base-package="com.dubbo.example.demo"/>
<bean class="com.dubbo.example.demo.HelloService" init-method="xmlinit" destroy-method="xmlDestory"/>
應用啟動方法如下:
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-provider.xml");
context.start();
context.close();
程式輸出結果如下所示:
最後採用圖示說明總結以上結果:
四、原始碼解析
不知道各位讀者有沒有猜對這幾種方式的執行順序,下面我們就從原始碼角度解析 Spring 內部處理的順序。
4.1、初始化過程
使用 ClassPathXmlApplicationContext
啟動 Spring 容器,將會呼叫 refresh
方法初始化容器。初始化過程將會建立 Bean
。最後當一切準備完畢,將會發送 ContextRefreshedEvent
。當容器初始化完畢,呼叫 context.start()
就傳送 ContextStartedEvent
事件。
refresh
方法原始碼如下:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//... 忽略無關程式碼
// 初始化所有非延遲初始化的 Bean
finishBeanFactoryInitialization(beanFactory);
// 傳送 ContextRefreshedEvent
finishRefresh();
//... 忽略無關程式碼
}
}
一路跟蹤 finishBeanFactoryInitialization
原始碼,直到 AbstractAutowireCapableBeanFactory#initializeBean
,原始碼如下:
protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
// 呼叫 BeanPostProcessor#postProcessBeforeInitialization 方法
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
// 初始化 Bean
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
}
}
BeanPostProcessor
將會起著攔截器的作用,一旦 Bean 符合條件,將會執行一些處理。這裡帶有 @PostConstruct
註解的 Bean
都將會被 CommonAnnotationBeanPostProcessor
類攔截,內部將會觸發 @PostConstruct
標註的方法。
接著執行 invokeInitMethods
,方法如下:
protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)
throws Throwable {
boolean isInitializingBean = (bean instanceof InitializingBean);
if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
// 省略無關程式碼
// 如果是 Bean 繼承 InitializingBean,將會執行 afterPropertiesSet 方法
((InitializingBean) bean).afterPropertiesSet();
}
if (mbd != null) {
String initMethodName = mbd.getInitMethodName();
if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
!mbd.isExternallyManagedInitMethod(initMethodName)) {
// 執行 XML 定義 init-method
invokeCustomInitMethod(beanName, bean, mbd);
}
}
}
如果 Bean
繼承 InitializingBean
介面,將會執行 afterPropertiesSet
方法,另外如果在 XML 中指定了 init-method
,也將會觸發。
上面原始碼其實都是圍繞著 Bean
建立的過程,當所有 Bean
建立完成之後,呼叫 context#start
將會發送 ContextStartedEvent
。這裡原始碼比較簡單,如下:
public void start() {
getLifecycleProcessor().start();
publishEvent(new ContextStartedEvent(this));
}
4.2、銷燬過程
呼叫 ClassPathXmlApplicationContext#close
方法將會關閉容器,具體邏輯將會在 doClose
方法執行。
doClose
這個方法首先發送 ContextClosedEvent
,然再後開始銷燬 Bean
。
靈魂拷問:如果我們顛倒上面兩者順序,結果會一樣嗎?
doClose
原始碼如下:
protected void doClose() {
if (this.active.get() && this.closed.compareAndSet(false, true)) {
// 省略無關程式碼
try {
// Publish shutdown event.
publishEvent(new ContextClosedEvent(this));
}
catch (Throwable ex) {
logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
}
// 銷燬 Bean
destroyBeans();
// 省略無關程式碼
}
}
destroyBeans
最終將會執行 DisposableBeanAdapter#destroy
,@PreDestroy
、DisposableBean
、destroy-method
三者定義的方法都將會在內部被執行。
首先執行 DestructionAwareBeanPostProcessor#postProcessBeforeDestruction
,這裡方法類似與上面 BeanPostProcessor
。
@PreDestroy
註解將會被 CommonAnnotationBeanPostProcessor
攔截,這裡類同時也繼承了 DestructionAwareBeanPostProcessor
。
最後如果 Bean
為 DisposableBean
的子類,將會執行 destroy
方法,如果在 xml 定義了 destroy-method
方法,該方法也會被執行。
public void destroy() {
if (!CollectionUtils.isEmpty(this.beanPostProcessors)) {
for (DestructionAwareBeanPostProcessor processor : this.beanPostProcessors) {
processor.postProcessBeforeDestruction(this.bean, this.beanName);
}
}
if (this.invokeDisposableBean) {
// 省略無關程式碼
// 如果 Bean 繼承 DisposableBean,執行 destroy 方法
((DisposableBean) bean).destroy();
}
if (this.destroyMethod != null) {
// 執行 xml 指定的 destroy-method 方法
invokeCustomDestroyMethod(this.destroyMethod);
}
else if (this.destroyMethodName != null) {
Method methodToCall = determineDestroyMethod();
if (methodToCall != null) {
invokeCustomDestroyMethod(methodToCall);
}
}
}
五、總結
init-method/destroy-method
這種方式需要使用 XML 配置檔案或單獨註解配置類,相對來說比較繁瑣。而InitializingBean/DisposableBean
這種方式需要單獨繼承 Spring 的介面實現相關方法。@PostConstruct/@PreDestroy
這種註解方式使用方式簡單,程式碼清晰,比較推薦使用這種方式。
另外 ContextStartedEvent/ContextClosedEvent
這種方式比較適合在一些整合框架使用,比如 Dubbo 2.6.X 優雅停機就是用改機制。
六、Spring 歷史文章推薦
1、Spring 註解程式設計之註解屬性別名與覆蓋
2、Spring 註解程式設計之 AnnotationMetadata
3、Spring 註解程式設計之模式註解
4、緣起 Dubbo ,講講 Spring XML Schema 擴充套件機制
歡迎關注我的公眾號:程式通事,獲得日常乾貨推送。如果您對我的專題內容感興趣,也可以關注我的部落格:studyidea.cn
相關推薦
&#128584;羞,Spring Bean 初始化/銷燬竟然有這麼多姿勢
文章來源:http://1t.click/bfHN 一、前言 日常開發過程有時需要在應用啟動之後載入某些資源,或者在應用關閉之前釋放資源。Spring 框架提供相關功能,圍繞 Spring Bean 生命週期,可以在 Bean 建立過程初始化資源,以及銷燬 Bean 過程釋放資源。Spring 提供多
羞,Java 字串拼接竟然有這麼多姿勢
二哥,我今年大二,看你分享的《阿里巴巴 Java 開發手冊》上有一段內容說:“迴圈體內,拼接字串最好使用 StringBuilder 的 append 方法,而不是 + 號操作符。”到底為什麼啊,我平常一直就用的‘+’號操作符啊!二哥有空的時候能否寫一篇文章分析一下呢? 就在昨天,一位叫小菜的讀者微信我說
spring(bean初始化、銷燬、注入,作用域)
IOC:控制反轉,也稱為依賴注入(DI)。這是一個過程。通常我們需要一個物件的時候,是主動建立物件,並且主動傳入到需要使用該物件的地方。而IOC則是由容器建立物件,注入到我們需要使用該物件的位置,兩者相比,一個主動,一個被動,被動的則是稱為依賴注入(控制反轉,由主動建立改為被動注入)。IO
【Spring:FactoryBean介面】實現FactoryBean介面,Spring在初始化bean時有何不同
問題描述: 最近想要再次熟悉一下阿里中介軟體HSF的用法,在消費HSF時需要在Spring的配置檔案中進行如下配置: <bean id="myClassB" class="com.taobao.hsf.app.spring.util.HSFSpri
Spring bean初始化與銷毀的幾種方式和區別
pack ack 構造 rop struct service() throws esc println 1. <bean> 元素的 init-method/destroy-method屬性指定初始化之後 /銷毀之前調用的操作方法 2. 指定方法上加上@PostC
Spring Bean初始化之後/銷燬之前執行指定方法
關於在spring 容器初始化 bean 和銷燬前所做的操作定義方式有三種: 通過@PostConstruct 和 @PreDestroy 方法 實現初始化和銷燬bean之前進行的操作 通過 在xml中定義init-method 和 destory-metho
Spring Bean初始化之後執行指定方法
轉載:https://blog.csdn.net/forever7107/article/details/76446544 常用的設定方式有以下三種: 通過實現 InitializingBean/DisposableBean 介面來定製初始化之後/銷燬之前的操作方法; 通過 <bean&g
【問題記錄】eclipse啟動web專案時,spring會初始化兩次
背景:一個tomcat,一個eclipse,一個SSM框架的web專案。在eclipse中新建tomcat伺服器,預設配置,然後在伺服器配置中將Server Locations改成Use Tomcat
Spring bean初始化原理詳解
一、 閒言 使用spring已經多年,卻從來沒有仔細研究過spring bean的初始化過程以及原理。知其然而不知其所以然,當面遇到比較深度的問題的時候,就無法解決或者需要花費大量問題方可解決。 二、 目的 本文主要想解決以下幾個問題,希望大家看完本文以後,能得出答案
Spring Bean 初始化之InitializingBean, init-method 和 PostConstruct
概述從介面的名字上不難發現,InitializingBean 的作用就是在 bean 初始化後執行定製化的操作。Spring 容器中的 Bean 是有生命週期的,Spring 允許在 Bean 在初始化完成後以及 Bean 銷燬前執行特定的操作,常用的設定方式有以下三種:通過實現 InitializingBe
spring-bean初始和銷燬之前之後的操作
使用註解定義Bean的初始化和銷燬 Spring初始化bean或銷燬bean時,有時需要作一些處理工作,因此spring可以在建立和拆卸bean的時候呼叫bean的兩個生命週期方法。 回顧配置檔案的寫法:<bean id=“foo” class=“...Foo”
spring容器初始化bean之後或銷燬bean之前,能做的操作
通過 <bean> 標籤 init-method 初始化bean之後呼叫的方法 destroy-method 銷燬bean之前呼叫的操作方法 <bean id="initQuartzJob" class="com.upinc
Spring管理的bean初始化方法的三種方式,以及@PostConstruct不起作用的原因
1:Spring 容器中的 Bean 是有生命週期的,spring 允許 Bean 在初始化完成後以及銷燬前執行特定的操作。下面是常用的三種指定特定操作的方法: 通過實現InitializingBean/DisposableBean 介面來定製初始化之後/銷燬之前的操作
Spring cglib 初始化 ExceptionInInitializerError,new Enhancer() 異常
ali ctc ant pan sta span pre get jar 解決辦法:更換 spring-cglib-repack-*.*.jar 包 java.lang.ExceptionInInitializerError at org.springfra
Spring啟動流程(四)之Bean初始化前後的一些操作
【Spring原始碼分析】非懶載入的單例Bean初始化前後的一些操作 再看AbstractApplicationContext的refresh方法中的細節: Spring預設載入的兩個Bean,systemProperties和systemEnvironment,
spring ioc---定製bean初始化和銷燬時的回撥函式
約莫有4種方式定製bean初始化和銷燬時的回撥函式,如下表格. 方式 說明 使用beans標籤的屬性 beans標籤中,使用以下兩個屬性, `default-init-method`和`default-destroy-m
【Spring】Springboot監聽器,啟動之後初始化工作
package com.laplace.laplace.common.starter.config; import java.io.IOException; import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springfram
Spring中Bean初始化的三種方法
常用的設定方式有以下三種: 通過實現 InitializingBean/DisposableBean 介面來定製初始化之後/銷燬之前的操作方法; 通過 <bean> 元素的 init-method/destroy-method屬性指定初始化之後 /銷燬之前呼叫的
Spring何時初始化bean
今天在看《spring原始碼深度解析》第五章關於bean載入的部分,跟蹤原始碼的過程中產生一個困惑。就是在我的程式碼中呼叫getBean以前,在載入xml配置檔案的時候對應的bean就已經進行了初始化。 程式碼如下: Main.java public cl
spring中bean初始化過程
在傳統的Java應用中,Bean的生命週期非常簡單。Java的關鍵詞new用來例項化Bean(或許他是非序列化的)。這樣就夠用了。相反,Bean 的生命週期在Spring容器中更加細緻。理解Spring Bean的生命週期非常重要,因為你或許要利用Spring提供的機會來訂製