一、前言
Lifecycle是Spring中最基礎的生命週期接口,該接口定義了容器啓動和停止的方法。方便開發者擴展自己的特定邏輯,比如啓動和停止某些後臺進程。
SmartLifecycle是對Lifecycle的一個擴展接口,當我們實現SmartLifecycle接口時,發現需要實現很多方法。很多同學不理解這些方法是幹嘛的,今天我們來一起探討下~
二、先說Lifecycle
Lifecycle常用來管理一個組件的啓動和停止,這個組件可以是一個線程、或者是一件事情。有同學可能會有這樣的疑惑:開始和停止的邏輯寫在一個bean的初始化方法和銷燬方法中不可以了嗎,爲什麼要實現個Lifecycle接口呢?這裏說明一下,bean的初始化方法和銷燬方法是Bean生命週期級別的;而Lifecycle是容器生命週期級別的。
我們來看下使用步驟~
1. 定義一個類並實現Lifecycle接口
public class MyLifecycle implements Lifecycle {
/**
* A 組件的運行狀態
*/
private volatile boolean running = false;
/**
* 容器啓動後調用
*/
@Override
public void start() {
System.out.println("lifecycle 容器啓動完成,啓動A組件...");
running = true;
}
/**
* 容器停止時調用
*/
@Override
public void stop() {
System.out.println("lifecycle 收到關閉容器的信號,關閉A組件...");
running = false;
}
/**
* 檢查此組件是否正在運行。
* 1. 只有該方法返回false時,start方法纔會被執行。
* 2. 只有該方法返回true時,stop(Runnable callback)或stop()方法纔會被執行。
*/
@Override
public boolean isRunning() {
System.out.println("lifecycle 檢查A組件的運行狀態:" + running);
return running;
}
}
Spring每次調用start()和stop()方法之前,都會調用下isRunning()方法。來檢測下這個A組件,是否已經啓動或停止過了。如果通過isRunning()方法,檢測到A組件已經是運行狀態了,就無需再調用start()方法啓動A組件。
2. 把MyLifecycle交給Spring管理
<bean id="myLifecycle" class="com.kaka.spring.pojo.custom.MyLifecycle"/>
以上配置在lifecycle.xml文件中
3. 執行
@Test
public void lifecycleTest() {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext(
"lifecycle.xml");
// 必須顯示調用
classPathXmlApplicationContext.start();
classPathXmlApplicationContext.stop();
}
運行結果
這裏強調一下,一定要顯示調用容器的start()和stop()方法,Lifecycle的接口方法纔會被執行。但是一般項目中(比如ssm和springboot),都不會顯示調用容器的start()方法,所以就有了SmartLifecycle接口的出現。
三、再談SmartLifecycle
SmartLifecycle是Lifecycle的一個子接口,比Lifecycle有更豐富的功能。其中有兩點最重要的改進:
- 無需容器顯示調用start()方法,就可以回調SmartLifecycle接口的start()
- 容器中如果有多個SmartLifecycle實例,可以方便控制調用順序。
用法和Lifecycle一樣,我們來看下
1. 定義一個類實現SmartLifecycle接口
public class MySmartLifecycle implements SmartLifecycle {
private volatile boolean running = false;
/**
* 如果該`Lifecycle`類所在的上下文在調用`refresh`時,希望能夠自己自動進行回調,則返回`true`* ,
* false的值表明組件打算通過顯式的start()調用來啓動,類似於普通的Lifecycle實現。
*/
@Override
public boolean isAutoStartup() {
return true;
}
/**
* 很多框架中的源碼中,都會把真正邏輯寫在stop()方法內。
* 比如quartz和Redis的spring支持包
*
* @param callback
*/
@Override
public void stop(Runnable callback) {
System.out.println("smartLifecycle stop runnable 容器停止...");
stop();
callback.run();
}
@Override
public void start() {
System.out.println("smartLifecycle 容器啓動完成 ...");
running = true;
}
@Override
public void stop() {
System.out.println("smartLifecycle stop 容器停止 ...");
running = false;
}
@Override
public boolean isRunning() {
System.out.println("smartLifecycle 檢查運行狀態 ...");
return running;
}
/**
* 階段值 越小越靠前執行start()方法,越靠後執行stop()方法
*
* @return
*/
@Override
public int getPhase() {
return 0;
}
}
一共需要實現6個方法,確實要不少啊,不是start()和stop()就行了嗎?好吧,我們一個個解釋下~
方法名 | 描述 |
---|---|
isAutoStartup() | 這個一定要返回true,不然就跟Lifecycle一樣了。 |
getPhase() | 控制多個SmartLifecycle的回調順序的,返回值越小越靠前執行start()方法,越靠後執行stop()方法 |
isRunning() | 與Lifecycle接口中的功能一樣,用來判你的斷組件是否在運行。 |
start() | 與Lifecycle接口中的功能一樣,當刷新容器(也就是啓動完成)時調用。 |
stop(Runnable callback) | 當容器停止時,回調該方法。當執行完你自定義的邏輯後,一定要調用下callback.run(); 這個是爲了,告訴容器你已停止自己的組件完成。 這裏多說一點,很多源碼會在該方法內僅寫兩行代碼,參考上面例子。一行是stop();把真正的邏輯轉發到stop()這個方法。另一行就是必須調用的callback.run(); |
stop() | 不會被Spring框架調用到! |
2. 把MySmartLifecycle交給Spring管理
<bean id="mySmartLifecycle" class="com.kaka.spring.pojo.custom.MySmartLifecycle"/>
以上配置在lifecycle.xml文件中
3. 執行
@Test
public void lifecycleTest() {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext(
"lifecycle.xml");
// 模擬框架中的停止方法
classPathXmlApplicationContext.stop();
}
執行結果
四、總結
- 需要根據Spring容器的生命週期,來做一些自己的邏輯時,一般都會選擇自定義一個類,實現SmartLifecycle這個接口。很少有人會使用Lifecycle
- 容器中有多個SmartLifecycle時,可以使用接口中的getPhase()方法控制回調的順序。方法返回值越小,越靠前執行start()方法,越靠後執行stop()方法
- SmartLifecycle這個接口的isAutoStartup()方法,一定要返回true,容器啓動時纔會回調SmartLifecycle的start()方法。
- stop(Runnable callback)方法是有超時時間的,默認爲30s。可以通過以下方式設置超時時間
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>