JAVA代码中如何感知到JVM退出
一.需求
当JVM退出的时候,把内存中的某块数据写入到DB。分析这个需求,我们需要让应用感知到JVM准备退出了,然后把数据写入DB,然后JVM继续执行退出…
二.实现
- 基于Spring的DisposableBean接口来实现
我们只要在自己的应用编写一个bean,实现DisposableBean这个接口中的destroy方法,然后在destroy方法中编写清理逻辑即可。要是我们不给Spring容器注册这样一个JVM关闭的钩子,JVM关闭的时候destroy方法同样不会被调用。
1 2 3 | ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("....xml"); // 注册JVM关闭时回调的钩子到Spring容器 ctx.registerShutdownHook(); |
必须在应用中给Spring容器注册这样关闭钩子,负责bean的destroy方法在JVM退出的时候是没法调用的。
另外,如果我们的应用是一个web应用,我们在停止web应用是,该应用中实现了DisposableBean这个接口的bean的destroy方法也会被调用,这个被调用的原因不是由于创建Spring容器时注册了关闭JVM回调的钩子,而是因为关闭web应用时会回调ServletContextListener的contextDestroyed方法,ServletContextListener的实现类是ContextLoaderListener,contextDestroyed的实现如下
1 2 3 4 5 6 7 8 9 10 11 | public class ContextLoaderListener implements ServletContextListener { private ContextLoader contextLoader; ... public void contextDestroyed(ServletContextEvent event) { if (this.contextLoader != null) { // 关闭Spring容器 this.contextLoader.closeWebApplicationContext(event.getServletContext()); } } ... } |
我们再看看ContextLoader的closeWebApplicationContext方法实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public class ContextLoader { //.... public void closeWebApplicationContext(ServletContext servletContext){ servletContext.log("Closing Spring root WebApplicationContext"); try { if (this.context instanceof ConfigurableWebApplicationContext){ // 销毁servlet的上下文,调用Spring容器的close方法 ((ConfigurableWebApplicationContext) this.context).close(); } } finally { // ... } } //... } |
因此web应用中不需要给Spring容器注册JVM关闭的钩子,代码中依然能够感知到JVM的关闭。
- 直接基于Runtime的addShutdownHook方法来实现
1 2 3 4 5 6 7 | Runtime.getRuntime().addShutdownHook(new Thread(){ @Override public void run() { System.out.println("JVM EXIT..."); } }); |
JVM退出的时候会回调我们注册的这个钩子,Spring注册JVM关闭的回调也是这么搞的,具体AbstractApplicationContext.java中源代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext, DisposableBean { .... /** * Register a shutdown hook with the JVM runtime, closing this context * on JVM shutdown unless it has already been closed at that time. * <p>Delegates to <code>doClose()</code> for the actual closing procedure. * @see java.lang.Runtime#addShutdownHook * @see #close() * @see #doClose() */ public void registerShutdownHook() { if (this.shutdownHook == null) { // No shutdown hook registered yet. this.shutdownHook = new Thread() { public void run() { doClose(); } }; Runtime.getRuntime().addShutdownHook(this.shutdownHook); } } .... } |
三.总结
回调思想
回调也是实现程序解耦的一种思路,在平时开发的过程中要注意回调的使用,回调的调用面向接口,回调的实现交给第三方来实现,我们也可提供缺省的回调实现。可扩展性
自己实现的框架或一定要提供可扩展的地方,方便满足别人个性化的一些需求。