高級Java開發者都知道的Hook鉤子函數,你還不知道嗎?

什麼是鉤子函數,在學習鉤子函數之前,小編先提一個問題。

請問在Spring中,如果JVM異常終止,Spring是如何保證會釋放掉佔用的資源,比如說數據庫連接等資源呢?

鉤子函數非常簡單,簡單到小編只用摘抄一段Spring代碼即可。走你,現在開始。

問題

Spring 容器中 Bean 在什麼時候執行銷燬方法?

我們知道在Spring中定義銷燬方法有兩種方式

  1. 實現 DisposableBeandestroy 方法。
  2. 使用 @PreDestroy 註解修飾方法
@Component
public class DataCollectBean implements DisposableBean {

    /**
     * 第一種方法實現 DisposableBean#destroy方法
     *
     * @throws Exception 異常
     */
    @Override
    public void destroy() throws Exception {
        System.err.println("執行銷燬方法");
    }

    /**
     * 第二種方法使用PreDestroy註解聲明銷燬方法
     */
    @PreDestroy
    public void customerDestroy() {
        System.err.println("執行自定義銷燬方法");
    }


}

那麼在什麼時候執行銷燬方法?

  1. 主動執行銷燬bean
    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args);
        DataCollectBean bean = run.getBean(DataCollectBean.class);
        //1. 主動銷燬bean
        run.getBeanFactory().destroyBean(bean);
    }
  1. JVM關閉時候自動執行銷燬方法。

這裏就要用到鉤子函數了, Spring 的鉤子函數在 AbstractApplicationContext#shutdownHook屬性

如果我們是SpringBoot項目我們看到在SpringApplication啓動時候會註冊一個鉤子函數

如何定義鉤子函數?

簡直太簡單了,沒有任何學習成本。一行代碼就能搞定。

public class HooksTester {
    public static void main(String[] args) {
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("鉤子函數執行");
            }
        }));
        //當主動關閉應用
        while (true);
    }
}

觸發鉤子函數的場景

只要不是機器斷電,強制kill -9 強制殺進程,都會觸發。

鉤子函數能做什麼?

正如上圖所示優雅停機,在項目將要關閉時候,主動釋放程序佔用的資源信息,釋放db連接池的連接等其他佔用的資源信息。
如果我們是 Spring 項目其實我們不用自己定義鉤子函數,我們只要使用Spring提供給我們的銷燬方法即可。因爲
Spring定義的鉤子函數中會去執行, DisposableBean.destory() 和被 PreDestroy 修飾的方法。

我們看下源碼

protected void doClose() {
		// Check whether an actual close attempt is necessary...
		if (this.active.get() && this.closed.compareAndSet(false, true)) {
			if (logger.isDebugEnabled()) {
				logger.debug("Closing " + this);
			}

			LiveBeansView.unregisterApplicationContext(this);

			try {
				// Publish shutdown event.
				publishEvent(new ContextClosedEvent(this));
			}
			catch (Throwable ex) {
				logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
			}

			// Stop all Lifecycle beans, to avoid delays during individual destruction.
			if (this.lifecycleProcessor != null) {
				try {
					this.lifecycleProcessor.onClose();
				}
				catch (Throwable ex) {
					logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
				}
			}

			// Destroy all cached singletons in the context's BeanFactory.
			destroyBeans();

			// Close the state of this context itself.
			closeBeanFactory();

			// Let subclasses do some final clean-up if they wish...
			onClose();

			// Reset local application listeners to pre-refresh state.
			if (this.earlyApplicationListeners != null) {
				this.applicationListeners.clear();
				this.applicationListeners.addAll(this.earlyApplicationListeners);
			}

			// Switch to inactive.
			this.active.set(false);
		}
	}

可以看到:doClose()方法會執行bean的destroy(),也會執行SmartLifeCycle的stop()方法,我們就可以通過重寫這些方法來實現對象的關閉,生命週期的管理,實現平滑shutdown

感謝您的閱讀,本文由 程序猿升級課 版權所有。如若轉載,請註明出處:程序猿升級課(https://blog.springlearn.cn/)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章