什麼是Spring的擴充點?
這個問題讓我很深刻,記得之前有一個面試就被問到有沒有使用過。
那他是什麼?
先來看下Spring容器的加載過程
可以看到Bean從無到有主要是經曆了四個步驟
就是在成熟态的時候,在初始化生命周期執行回調方法
主要是以接口或者注解的形式對外提供,注入到IOC容器中,完成對應的功能。
哪些場景下,我們需要使用退出前銷毀
主要是希望在銷毀之前在做一些事情,比如像池化技術正确的斷開,JVM記憶體回收,還有業務邏輯執行。
業務場景
直接進入正題,我先說一說我的業務場景,在執行任務A的時候,這時候服務重新開機了,因為任務A加了分布式鎖,是以在
重新開機服務的時候,補償機制拿到了任務A發現鎖依然被占用着,是以我就希望能夠在應用關閉之前把鎖給釋放掉,減少
對補償機制的影響。
補充:這裡其實也可以用Redisson,來進行鎖續期,一段時間過後自己釋放,但是系統中更多時候使用簡單的分布式
鎖就可以滿足,避免引入Redisson這麼重的架構。
解決方案
- 将目前執行任務的redis鎖記錄下來
- 在Spring應用關系的時候,調用銷毀方法進行鎖的釋放
- 采用SmartLifecycle和DisposableBean互相配合來執行destroy()方法
具體實作:
@Service
public class UserServiceImpl implements UserService, DisposableBean, SmartLifecycle {
private volatile boolean running = false;
private List<String> lockKeys = new ArrayList<>();
@Resource
HelloService helloService;
@Override
public void get() {
String key = "redis:key";
//僞代碼
RedissonUtil.lock(key);
try {
lockKeys.add(key);
} catch (Exception ex){
ex.printStackTrace();
//...
} finally {
RedissonUtil.unlock(key);
lockKeys.remove(key);
}
}
@Override
public void destroy() {
// 删除正在執行中的key
RedissonUtil.deletes(lockKeys);
running = false;
}
@Override
public void start() {
System.out.println("start >>>>");
running = true;
}
@Override
public void stop() {
System.out.println("stop >>>>");
// 删除正在執行中的key
RedissonUtil.deletes(lockKeys);
}
@Override
public boolean isRunning() {
return running;
}
@Override
public int getPhase() {
return Integer.MAX_VALUE;
}
}
複制代碼
利用DisposableBean和SmartLifecycle進行雙重的銷毀機制,如果已經執行了DisposableBean的銷毀方法
那可以修改running的值為false,就不會再進行stop()的執行了
Spring執行關閉的時機
- JVM關閉
- 對象銷毀時候
- 容器停止
關閉前執行銷毀方法有哪些
- DisposableBean
調用時機:Bean對象銷毀的時候
@Service
public class UserServiceImpl implements UserService, DisposableBean {
@Override
public void destroy() {
System.out.println("destroy>>>>>");
}
}
複制代碼
- SmartLifecycle
調用時機:Spring容器發出關閉通知
@Service
public class UserServiceImpl implements UserService, SmartLifecycle {
@Override
public void start() {
System.out.println("start >>>>");
running = true;
}
@Override
public void stop() {
System.out.println("stop >>>>");
}
@Override
public boolean isRunning() {
return running;
}
@Override
public int getPhase() {
return Integer.MAX_VALUE;
}
}
複制代碼
- InitializingBean
這個方式比較特殊,就是在初始化的時候,提前設定好了鈎子函數addShutdownHook
調用時機:監聽到JVM關閉
@Service
public class UserServiceImpl implements UserService, InitializingBean {
@Override
public void afterPropertiesSet() {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
helloService.get();
System.out.println("addShutdownHook>>>>>");
}));
}
}
複制代碼
- @PreDestroy注解
@PreDestroy
public void preDestroy(){
System.out.println("PreDestroy>>>>");
}
複制代碼
- Xml和@Bean綁定destoryMethod方法
對比執行結果:
SmartLifecycle > @PreDestroy,DisposableBean > addShutdownHook
2022-09-05 23:06:04.046 INFO 11807 --- [ main] c.l.d.SpringBootDemoDockerApplication : Started SpringBootDemoDockerApplication in 1.4 seconds (JVM running for 1.752)
ApplicationRunner>>>>>
CommandLineRunner>>>項目啟動完畢後,倒數10秒關閉
thread1...
thread1...
thread1...
thread1...
stop >>>>
2022-09-05 23:06:14.054 INFO 11807 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
PreDestroy>>>>
destroy>>>>>
thread1...
thread1...
get
addShutdownHook>>>>>
複制代碼
SmartLifecycle接口源碼
了解一下SmartLifecycle接口到底由哪些組成的
/**
* 當上下文被重新整理(所有對象已被執行個體化和初始化之後)時,将調用該方法
* isAutoStartup預設為true則調用start,否則需要自己手動調用
*/
@Override
public void start() {
System.out.println("start >>>>");
running = true;
}
/**
* 接口Lifecycle子類的方法,隻有非SmartLifecycle的子類才會執行該方法。
* 1. 該方法隻對直接實作接口Lifecycle的類才起作用,對實作SmartLifecycle接口的類無效。
* 2. 方法stop()和方法stop(Runnable callback)的差別隻在于,後者是SmartLifecycle子類的專屬。
*/
@Override
public void stop() {
System.out.println("stop >>>>");
}
/**
* 隻有該方法傳回false時,start方法才會被執行
* 隻有該方法傳回true時,stop(Runnable callback)或stop()方法才會被執
* @return
*/
@Override
public boolean isRunning() {
return running;
}
/**
* 傳回 Integer.MAX_VALUE 僅表明
* 我們将是第一個關閉的 bean 和最後一個啟動的 bean
* 關閉容器的第一時間調用stop()方法
*/
@Override
public int getPhase() {
return Integer.MAX_VALUE;
}
/**
* 如果該`Lifecycle`類所在的上下文在調用`refresh`時,希望能夠自己自動進行回調,則傳回`true`,
* false的值表明元件打算通過顯式的start()調用來啟動,類似于普通的Lifecycle實作。
*/
@Override
public boolean isAutoStartup() {
return false;
}
/**
* SmartLifecycle子類的才有的方法,當isRunning方法傳回true時,該方法才會被調用。
* 很多架構中的源碼中,都會把真正邏輯寫在stop()方法内。
*/
@Override
public void stop(Runnable callback) {
stop();
// 如果你讓isRunning傳回true,需要執行stop這個方法
// 在程式退出時,Spring的DefaultLifecycleProcessor會認為這個MySmartLifecycle沒有stop完成,
// 程式會一直卡着結束不了,等待一定時間(預設逾時時間30秒)後才會自動結束。
callback.run();
}
複制代碼
SmartLifecycle#isRunning判斷是否已經執行,false表示還未執行
則調用SmartLifecycle#start()執行
當關閉的時候isRunning為ture已經執行
則調用SmartLifecycle#stop()執行
學習MQ如何進行退出前優雅執行銷毀方法
DefaultRocketMQListenerContainer.class
public class DefaultRocketMQListenerContainer implements InitializingBean,
RocketMQListenerContainer, SmartLifecycle, ApplicationContextAware {
private final static Logger log = LoggerFactory.getLogger(DefaultRocketMQListenerContainer.class);
private boolean running;
...
@Override
public void destroy() {
this.setRunning(false);
if (Objects.nonNull(consumer)) {
consumer.shutdown();
}
log.info("container destroyed, {}", this.toString());
}
@Override
public boolean isAutoStartup() {
return true;
}
@Override
public void stop(Runnable callback) {
stop();
callback.run();
}
@Override
public void start() {
if (this.isRunning()) {
throw new IllegalStateException("container already running. " + this.toString());
}
try {
consumer.start();
} catch (MQClientException e) {
throw new IllegalStateException("Failed to start RocketMQ push consumer", e);
}
this.setRunning(true);
log.info("running container: {}", this.toString());
}
@Override
public void stop() {
if (this.isRunning()) {
if (Objects.nonNull(consumer)) {
consumer.shutdown();
}
setRunning(false);
}
}
@Override
public boolean isRunning() {
return running;
}
private void setRunning(boolean running) {
this.running = running;
}
@Override
public int getPhase() {
// Returning Integer.MAX_VALUE only suggests that
// we will be the first bean to shutdown and last bean to start
return Integer.MAX_VALUE;
}
@Override
public void afterPropertiesSet() throws Exception {
initRocketMQPushConsumer();
this.messageType = getMessageType();
this.methodParameter = getMethodParameter();
log.debug("RocketMQ messageType: {}", messageType);
}
}
複制代碼
RocketMQ在這裡進行了幾個步驟需要我們關注
- 他将getPhase的值設定為最大,在容器關閉的第一時間調用stop()方法
-
同時實作了SmartLifecycle和RocketMQListenerContainer接口,分别實作了stop()和destroy()方法, 進行雙重關閉,如果和destroy()先執行了,則将running設定為false,不再執行stop()
原文連結:https://juejin.cn/post/7139920679683489823