Spring优雅关闭之:ShutDownHook

前言:

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

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

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

    我们的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_恐龙弟旺仔的博客-优快云博客  )关于SmartLifeCycleDemo的测试,那个也是输出了stop,但是是因为主动调用了applicationContext.stop()方法所以才输出的,我们当前并没有主动调用stop()方法

5.总结

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

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

参考:

Spring Framework Reference Documentation

代码地址:GitHub - kldwz/springstudy  

日拱一卒,砥砺前行!

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

实时获取最新动态

### Dubbo与Spring Boot整合教程 Dubbo 是一款高性能的 Java 分布式服务框架,而 Spring Boot 提供了一种快速构建微服务应用的方式。两者结合能够实现高效的服务治理和开发体验。 #### 1. 添加依赖 在 `pom.xml` 文件中引入必要的 Maven 依赖项: ```xml <dependencies> <!-- Spring Boot Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- Dubbo Spring Boot Starter --> <dependency> <groupId>com.alibaba.boot</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>2.7.8</version> </dependency> <!-- Zookeeper Client (推荐使用 Curator) --> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>5.0.0</version> </dependency> <!-- Logback Classic --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> </dependencies> ``` 上述代码片段展示了如何通过 Maven 构建工具来管理项目所需的依赖关系[^1]。 #### 2. 配置文件设置 创建并编辑 `application.properties` 或 `application.yml` 文件以定义 Dubbo 的核心参数: ##### application.yml 示例: ```yaml server: port: 8080 dubbo: scan-package: com.example.dubbodemo.service.impl protocol: name: dubbo port: 20880 registry: address: zookeeper://127.0.0.1:2181 application: name: demo-provider ``` 此部分配置指定了 Dubbo 应用的核心属性,例如协议名称、端口以及注册中心地址等信息[^4]。 #### 3. 创建接口和服务实现类 定义一个简单的服务接口及其对应的实现逻辑: ```java // 定义远程调用接口 public interface DemoService { String sayHello(String name); } // 实现该接口的具体业务功能 @Service(version = "1.0.0") // 注解表明这是一个 Dubbo Service public class DemoServiceImpl implements DemoService { @Override public String sayHello(String name) { return "Hello, " + name; } } ``` 以上代码展示了一个典型的 Dubbo 接口声明方式,并提供了具体的方法实现。 #### 4. 启动类编写 最后一步是在主应用程序入口处启用 Dubbo 功能支持: ```java @SpringBootApplication @Import(DubboConfiguration.class) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` 这段脚本初始化了整个 Spring Boot 环境的同时加载了 Dubbo 扩展组件。 --- ### 日志清理机制补充说明 为了防止内存泄漏,在程序终止前应妥善处理日志系统的资源释放工作。可以通过如下 XML 片段完成 logback 的优雅关闭操作[^3]: ```xml <configuration debug="true"> <shutdownHook/> </configuration> ``` 这确保即使 JVM 中断也能正常回收相关联的对象实例。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

恐龙弟旺仔

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

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

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

打赏作者

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

抵扣说明:

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

余额充值