Spring优雅关闭:ShutDownHook实现资源释放与异常处理

程序在异常结束的情况下,有可能无法及时释放某些资源。通过预埋钩子这种方式,能够在异常退出时进行捕获。
获取运行时对象。然后通过该对象调用 addShutdownHook 方法。
我们模拟一个异常情况。程序报错之后,钩子函数会开始执行。通过使用 Runtime 来添加钩子函数,就能够实现优雅停机。
public static void main(String[] args) throws Exception {
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
System.out.println("正在关闭一些连接资源...");
try {
Thread.sleep(1000);
} catch (InterruptedException ignore) {
}
}
}));
// 模拟执行业务耗时
int i = 0;
while (true) {
System.out.println("i am working...");
try {
Thread.sleep(1000);
} catch (InterruptedException ignore) {
}
// 模拟一个异常
if (i++ > 5) {
throw new Exception("error.");
}
}
}
这个钩子对 kill(kill -15) 是有效的,然而对 kill -9 却是无效的。所以在生产中不得已要使用 kill 时,通常会先使用 kill ,一般不会使用 kill -9 。
适用的场景:
Spring如何添加钩子函数,实现优雅关闭
ApplicationContext 注册了关闭钩子。
可以配置的应用上下文。 applicationContext = SpringApplication.run(SpringBootNonWebApplication.class, args);
applicationContext.registerShutdownHook();
ClassPathXmlApplicationContext 是一个用于加载 XML 配置文件的 Spring 应用上下文类。它可以从类路径中读取配置文件,并创建和管理 Spring 应用中的各种 Bean。通过使用 ClassPathXmlApplicationContext,开发人员可以方便地配置和启动 Spring 应用,而无需手动创建和管理 Bean 的实例。它提供了一种简单而有效的方式来管理 Spring 应用的依赖关系和配置信息。 applicationContext = new ClassPathXmlApplicationContext("beans.xml");
// 注册钩子
applicationContext.registerShutdownHook();
// 通过源码可以看到
@Override
public void registerShutdownHook() {
if (this.shutdownHook == null) {
尚未注册关闭钩子。
this.shutdownHook = new Thread() {
@Override
public void run() {
synchronized (startupShutdownMonitor) {
doClose();
}
}
};
// 也是通过这种方式来添加
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
}
重点是这个doClose()方法:
protected void doClose() {
检查是否有实际的关闭尝试是必要的……
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(ApplicationListener 处理 ContextClosedEvent 时抛出了异常。, ex);
}
停止所有的生命周期 Bean,以避免在单个销毁过程中出现延迟。
if (this.lifecycleProcessor != null) {
try {
this.lifecycleProcessor.onClose();
}
catch (Throwable ex) {
logger.warn(在上下文关闭时,从生命周期处理器抛出了异常。, ex);
}
}
在上下文的 BeanFactory 中销毁所有缓存的单例。
destroyBeans();
关闭这个上下文自身的状态。
closeBeanFactory();
如果子类愿意的话,让它们进行一些最后的清理工作。
onClose();
// Switch to inactive.
this.active.set(false);
}
}
doClose()方法会去执行 bean 的 destroy(),同时也会执行 SmartLifeCycle 的 stop()方法。我们能够通过对这些方法进行重写,借助对象生命周期的管理,从而实现平滑的 shutdown。
结合这一篇:
Spring 定义 Bean 的 init Method 有三种方式;Spring 定义 Bean 的 destroy Method 有三种方式。
当创建一个 bean 的时候,能够指定 initMethod 以及 destroyMethod 。
@Configuration
public class XxDataSourceConfiguration {
@Bean(initMethod = "myInitMethod", destroyMethod = "myDestroyMethod")
public XxDataSource getBean(){
return new XxDataSource();
}
}
class XxDataSource {
public void myInitMethod() {
System.out.println("我是一个init方法...");
}
public void myDestroyMethod() {
System.out.println("我是一个destroy方法...");
}
}
【Spring】Lifecycle的使用与源码分析
Spring 容器的生命周期由 LifeCycle 接口定义,凡是被 Spring 管理的对象都能够实现该接口。Spring 容器自身在启动和停止的时候,会调用 LifeCycle 接口中所定义的方法,即【start()】和【stop()】。
// 1)不注册,只创建容器,结果如下:
只执行了 LifeCycle.start()方法,没有其他额外操作。
// 2)注册钩子
start
执行了 LifeCycle.stop()方法,从而停止了 Runnable 。
我们当前讨论的是 Spring 的非 Web 程序。如果是 Web 程序,就不需要我们去注册钩子函数,因为 Spring 的 Web 程序自身已经有了相关的代码,能够实现优雅关闭。
SpringBoot 项目在异常情况下会首先执行 destroyMethod 方法。