天天看點

Spring容器關閉執行銷毀方法有幾種?

作者:Java面試秘籍

什麼是Spring的擴充點?

這個問題讓我很深刻,記得之前有一個面試就被問到有沒有使用過。

那他是什麼?

先來看下Spring容器的加載過程

可以看到Bean從無到有主要是經曆了四個步驟

Spring容器關閉執行銷毀方法有幾種?

就是在成熟态的時候,在初始化生命周期執行回調方法

Spring容器關閉執行銷毀方法有幾種?

主要是以接口或者注解的形式對外提供,注入到IOC容器中,完成對應的功能。

哪些場景下,我們需要使用退出前銷毀

主要是希望在銷毀之前在做一些事情,比如像池化技術正确的斷開,JVM記憶體回收,還有業務邏輯執行。

業務場景

直接進入正題,我先說一說我的業務場景,在執行任務A的時候,這時候服務重新開機了,因為任務A加了分布式鎖,是以在

重新開機服務的時候,補償機制拿到了任務A發現鎖依然被占用着,是以我就希望能夠在應用關閉之前把鎖給釋放掉,減少

對補償機制的影響。

補充:這裡其實也可以用Redisson,來進行鎖續期,一段時間過後自己釋放,但是系統中更多時候使用簡單的分布式

鎖就可以滿足,避免引入Redisson這麼重的架構。

解決方案

  1. 将目前執行任務的redis鎖記錄下來
  2. 在Spring應用關系的時候,調用銷毀方法進行鎖的釋放
  3. 采用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執行關閉的時機

  1. JVM關閉
  2. 對象銷毀時候
  3. 容器停止

關閉前執行銷毀方法有哪些

  1. DisposableBean

調用時機:Bean對象銷毀的時候

@Service
public class UserServiceImpl implements UserService, DisposableBean {

    @Override
    public void destroy() {
        System.out.println("destroy>>>>>");
    }
}
複制代碼           
  1. 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;
    }
}
複制代碼           
  1. 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>>>>>");
        }));
    }
}
複制代碼           
  1. @PreDestroy注解
@PreDestroy
public void preDestroy(){
    System.out.println("PreDestroy>>>>");
}
複制代碼           
  1. 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在這裡進行了幾個步驟需要我們關注

  1. 他将getPhase的值設定為最大,在容器關閉的第一時間調用stop()方法
  2. 同時實作了SmartLifecycle和RocketMQListenerContainer接口,分别實作了stop()和destroy()方法, 進行雙重關閉,如果和destroy()先執行了,則将running設定為false,不再執行stop()

    原文連結:https://juejin.cn/post/7139920679683489823