转载自【Spring优雅关闭之:ShutDownHook】:https://blog.csdn.net/qq_26323323/article/details/89814410
程序在异常结束时,可能来不及释放一些资源,可以通过预埋钩子,当异常退出时进行捕获:
Runtime.getRuntime().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)
适用的场景:
-
程序正常退出
-
使用System.exit()
-
终端使用Ctrl+C触发的中断
-
系统关闭
-
OutofMemory宕机
-
使用Kill pid杀死进程(使用kill -9是不会被调用的)
Spring如何添加钩子函数,实现优雅关闭
ApplicationContext.registerShutdownHook();
ConfigurableApplicationContext applicationContext = SpringApplication.run(SpringBootNonWebApplication.class, args);
applicationContext.registerShutdownHook();
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
// 注册钩子
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。
结合这一篇:
Spring——定义Bean init Method与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的使用与源码分析
LifeCycle接口定义了Spring容器的生命周期,任何被Spring管理的对象都可以实现该接口。当Spring容器本身启动和停止时,会回调LifeCycle接口中定义的方法【start()、stop()】。
// 1)不注册,只创建容器,结果如下:
start // 只输出start,说明只执行了LifeCycle.start()方法
// 2)注册钩子
start
stop(Runnable) // 执行了LifeCycle.stop()方法
我们当前讨论的都是Spring非Web程序,如果是Web程序的话,不需要我们来注册钩子函数,Spring的Web程序已经有了相关的代码实现优雅关闭了。
SpringBoot项目也会在异常时先执行destroyMethod方法。