Spring优雅关闭之:ShutDownHook

前言:

    又是一个之前没关注过的课题,发现学习Spring官方文档还是有用的,一个个的知识点不断冒出来。

    意外的发现朱小厮朱小厮的博客_CSDN博客 大神也是CSDN重度患者,哈哈,向大神学习,好好写博客,应该有一天也可以出书的吧。

    闲话不多说了,先提出一个问题,什么叫做优雅关闭?

    我们的java程序运行在JVM上,有很多情况可能会突然崩溃掉,比如OOM、用户强制退出、业务其他报错。。。等一系列的问题可能导致我们的进程挂掉。如果我们的进程在运行一些很重要的内容,比如事务操作之类的,很有可能导致事务的不一致性问题。所以,实现应用的优雅关闭还是蛮重要的,起码我们可以在关闭之前做一些记录补救操作。

    

1.如何补救?

    在java程序中,可以通过添加关闭钩子函数,实现在程序退出时关闭资源、平滑退出的功能。

    如何做呢?

    主要就是通过Runtime.addShutDownHook(Thread hook)来实现的。下面我们来简单看一个示例

2.Runtime.addShutDownHook(Thread hook)

// 创建HookTest,我们通过main方法来模拟应用程序
public class HookTest {

    public static void main(String[] args) {

        // 添加hook thread,重写其run方法
        Runtime.getRuntime().addShutdownHook(new Thread(){
            @Override
            public void run() {
                System.out.println("this is hook demo...");
                // TODO
            }
        });

        int i = 0;
        // 这里会报错,我们验证写是否会执行hook thread
        int j = 10/i;
        System.out.println("j" + j);
    }
}

// res
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at hook.HookTest.main(HookTest.java:23)
this is hook demo...

Process finished with exit code 1

    总结:我们主动写了一个报错程序,在程序报错之后,钩子函数还是被执行了。经验证,我们是可以通过对Runtime添加钩子函数来做到优雅停机。

3.Runtime.addShutDownHook(Thread hook)应用场景

    既然JDK提供的这个方法可以注册一个JVM关闭的钩子函数,那么这个函数在什么情况下会被调用呢?上述我们展示了在程序异常情况下会被调用,还有没有其他场景呢?

    * 程序正常退出

    * 使用System.exit()

    * 终端使用Ctrl+C触发的中断

    * 系统关闭

    * OutofMemory宕机

    * 使用Kill pid杀死进程(使用kill -9是不会被调用的)

4.Spring如何添加钩子函数

    1)Spring添加钩子函数比较简单,如下

// 通过这种方式来添加钩子函数
ApplicationContext.registerShutdownHook();

// 通过源码可以看到,
@Override
public void registerShutdownHook() {
    if (this.shutdownHook == null) {
        // No shutdown hook registered yet.
        this.shutdownHook = new Thread() {
            @Override
            public void run() {
                synchronized (startupShutdownMonitor) {
                    doClose();
                }
            }
        };
        // 也是通过这种方式来添加
        Runtime.getRuntime().addShutdownHook(this.shutdownHook);
    }
}

// 重点是这个doClose()方法

protected void doClose() {
    // Check whether an actual close attempt is necessary...
    if (this.active.get() && this.closed.compareAndSet(false, true)) {
        if (logger.isInfoEnabled()) {
            logger.info("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();

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

    可以看到:doClose()方法会执行bean的destroy(),也会执行SmartLifeCycle的stop()方法,我们就可以通过重写这些方法来实现对象的关闭,生命周期的管理,实现平滑shutdown

    2)测试钩子

// 1.我们之前生命周期管理的SmartLifeCycleDemo    
// 参考Spring容器生命周期管理:SmartLifecycle

// 2.单元测试类,创建一个ApplicationContext
@Test
public void testXml(){
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
    // 注册钩子
    applicationContext.registerShutdownHook();
}    

// 我们可以对比下注册钩子与不注册的区别:
// 1)不注册,只创建容器,结果如下:
start // 只输出start,说明只执行了SmartLifeCycleDemo.start()方法
    
// 2)注册钩子
start
stop(Runnable) // 当main方法执行结束时,主动执行了SmartLifeCycleDemo.stop()方法

    总结:通过注册钩子函数,可以在程序停止前执行我们自定义的各种destroy()或者stop()方法,用于优雅关闭。

    注意:我们可以对比上一篇关于SmartLifeCycle的文章中(Spring容器生命周期管理:SmartLifecycle_恐龙弟旺仔的博客-CSDN博客  )关于SmartLifeCycleDemo的测试,那个也是输出了stop,但是是因为主动调用了applicationContext.stop()方法所以才输出的,我们当前并没有主动调用stop()方法

5.总结

    我们通过调用ApplicationContext.registerShutdownHook()来注册钩子函数,实现bean的destroy,Spring容器的关闭,通过实现这些方法来实现平滑关闭。

    注意:我们当前讨论的都是Spring非Web程序,如果是Web程序的话,不需要我们来注册钩子函数,Spring的Web程序已经有了相关的代码实现优雅关闭了。

参考:

Spring Framework Reference Documentation

代码地址:GitHub - kldwz/springstudy  

日拱一卒,砥砺前行!

欢迎关注微信公众号:开发者实用工具合集

实时获取最新动态

  • 4
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

恐龙弟旺仔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值