Spring优雅关闭之:ShutDownHook

前言:

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

    意外的发现朱小厮https://blog.csdn.net/u013256816/ 大神也是CSDN重度患者,哈哈,向大神学习,好好写博客,应该有一天也能够出书的吧。java

 

    闲话很少说了,先提出一个问题,什么叫作优雅关闭?git

    咱们的java程序运行在JVM上,有不少状况可能会忽然崩溃掉,好比OOM、用户强制退出、业务其余报错。。。等一系列的问题可能致使咱们的进程挂掉。若是咱们的进程在运行一些很重要的内容,好比事务操做之类的,颇有可能致使事务的不一致性问题。因此,实现应用的优雅关闭仍是蛮重要的,起码咱们能够在关闭以前作一些记录补救操做。github

    

1.如何补救?

    在java程序中,能够经过添加关闭钩子函数,实如今程序退出时关闭资源、平滑退出的功能。spring

    如何作呢?app

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

 

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的文章中(https://blog.csdn.net/qq_26323323/article/details/89814304  )关于SmartLifeCycleDemo的测试,那个也是输出了stop,可是是由于主动调用了applicationContext.stop()方法因此才输出的,咱们当前并无主动调用stop()方法

 

5.总结

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

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

 

参考:

https://docs.spring.io/spring/docs/4.3.23.RELEASE/spring-framework-reference/htmlsingle/ 

代码地址:https://github.com/kldwz/springstudy