文章來源:http://1t.click/bfHNhtml
平常開發過程有時須要在應用啓動以後加載某些資源,或者在應用關閉以前釋放資源。Spring 框架提供相關功能,圍繞 Spring Bean
生命週期,能夠在 Bean
建立過程初始化資源,以及銷燬 Bean
過程釋放資源。Spring 提供多種不一樣的方式初始化/銷燬 Bean
,若是同時使用這幾種方式,Spring 如何處理這幾者之間的順序?java
有沒有以爲標題很熟悉,沒錯標題模仿二哥 「@沉默王二」 文章羞,Java 字符串拼接居然有這麼多姿式。spring
首先咱們先來回顧一下 Spring 初始化/銷燬 Bean
幾種方式,分別爲:編程
init-method/destroy-method
InitializingBean/DisposableBean
@PostConstruct/@PreDestroy
ContextStartedEvent/ContextClosedEvent
PS: 其實還有一種方式,就是繼承 Spring
Lifecycle
接口。不過這種方式比較繁瑣,這裏就再也不分析。api
這種方式在配置文件文件指定初始化/銷燬方法。XML 配置以下app
<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 框架,使用就是這種方式。maven
這種方式須要繼承 Spring 接口 InitializingBean/DisposableBean
,其中 InitializingBean
用於初始化動做,而 DisposableBean
用於銷燬以前清理動做。使用方式以下:ide
@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...."); } }
這種方式相對於上面兩種方式來講,使用方式最簡單,只須要在相應的方法上使用註解便可。使用方式以下:post
@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
,否者註解不會生效。
這種方式使用 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 內部處理的順序。
使用 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)); }
調用 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 優雅停機就是用改機制。
6、Spring 歷史文章推薦
一、Spring 註解編程之註解屬性別名與覆蓋
二、Spring 註解編程之 AnnotationMetadata
三、Spring 註解編程之模式註解
四、緣起 Dubbo ,講講 Spring XML Schema 擴展機制
歡迎關注個人公衆號:程序通事,得到平常乾貨推送。若是您對個人專題內容感興趣,也能夠關注個人博客:studyidea.cn