在应用中,经常希望在JVM关闭时做一些清理的动作,本文陈述几种常用的实现方式。
1、JVM自带的shutdownHook
Runtime.getRuntime().addShutdownHook(new Thread(() -> log.info("shutdown hook, jvm demo")));
特点: jvm自带,使用方便,多个钩子间是并行执行的。
2、监听Spring的ContextClosedEvent
关于ContextClosedEvent等事件描述,可以参照以下示例(内容来自Spring官网)
Sr.No | Spring Built-in Events | Description |
1 | ContextRefreshedEvent | This event is published when the ApplicationContext is either initialized or refreshed. This can also be raised using the refresh() method on the ConfigurableApplicationContext interface. |
2 | ContextStartedEvent | This event is published when the ApplicationContext is started using the start() method on the ConfigurableApplicationContext interface. You can poll your database or you can restart any stopped application after receiving this event. |
3 | ContextStoppedEvent | This event is published when the ApplicationContext is stopped using the stop() method on the ConfigurableApplicationContext interface. You can do required housekeep work after receiving this event. |
4 | ContextClosedEvent | This event is published when the ApplicationContext is closed using the close() method on the ConfigurableApplicationContext interface. A closed context reaches its end of life; it cannot be refreshed or restarted. |
5 | RequestHandledEvent | This is a web-specific event telling all beans that an HTTP request has been serviced. |
实现ApplicationListener接口,监听ContextClosedEvent
@Component
@Slf4j
public class ShutdownHookDemo implements ApplicationListener<ContextClosedEvent> {
@Override
public void onApplicationEvent(ContextClosedEvent event) {
log.info("shutdown hook, ContextClosedEvent");
}
}
3、实现DisposableBean接口
对于DisposableBean接口
package org.springframework.beans.factory
public interface DisposableBean {
/**
* Invoked by the containing {@code BeanFactory} on destruction of a bean.
* @throws Exception in case of shutdown errors. Exceptions will get logged
* but not rethrown to allow other beans to release their resources as well.
*/
void destroy() throws Exception;
}
接口描述中这样写到
Interface to be implemented by beans that want to release resources on destruction. A BeanFactory will invoke the destroy method on individual destruction of a scoped bean
对于那些希望在Bean销毁时需要执行资源释放的Bean,可以实现该接口。
对每个单独的Bean,BeanFactory会负责调用destory方法。
@Component
@Slf4j
public class ShutdownHookDemo implements DisposableBean {
@Override
public void destroy() throws Exception {
log.info("shutdown hook, disposable bean");
}
}
4、使用注解@PreDestory
PreDestory注解位于包:**package **javax.annotation,对于PreDestory注解的描述
The PreDestroy annotation is used on methods as a callback notification to signal that the instance is in the process of being removed by the container. The method annotated with PreDestroy is typically used to release resources that it has been holding.
该注解的用途也是用于释放一些Bean持有的资源,当Bean的实例被容器移除时,会触发。
不过对于该注解的使用,是有一些限制条件的,在注解的注释中也有描述
• The method MUST NOT have any parameters except in the case of interceptors in which case it takes an InvocationContext object as defined by the Interceptors specification.(除了在interceptor情况下可以接收InvocationContext参数外,方法声明必须是无参的)
• The method defined on an interceptor class MUST HAVE one of the following signatures:(如果作为Interceptors,必须使用以下两种方法签名)
• void (InvocationContext)
• Object (InvocationContext) throws Exception
• The method defined on a non-interceptor class MUST HAVE the following signature:(如果不是作为Intercpetor,必须使用下面的方法签名)
• void ()
• The method on which PreDestroy is applied MAY be public, protected, package private or private. (方法访问权限可以是public/protected/private)
• The method MUST NOT be static.(方法能声明为static)
• The method MAY be final.(方法可以声明为final)
示例代码
@Component
@Slf4j
public class ShutdownHookDemo {
@PreDestroy
public void preDestroy() {
log.info("shutdown hook, pre destroy");
}
}
对于@PreDestory会存在一个不生效的情况,对于Scope为prototype的bean,Spring不会调用@PreDestory标记的方法,以下是官方的一些解答(stackoverflowhttps://stackoverflow.com/questions/16373276/predestroy-method-of-a-spring-singleton-bean-not-called)。
In contrast to the other scopes, Spring does not manage the complete lifecycle of a prototype bean**: the container instantiates, configures, and otherwise assembles a prototype object, and hands it to the client, with no further record of that prototype instance.(Spring并未对Prototype类型Bean的全生命周期进行管理)
Thus, although initialization lifecycle callback methods are called on all objects regardless of scope, in the case of prototypes, configured destruction lifecycle callbacks are not called. The client code must clean up prototype-scoped objects and release expensive resources that the prototype bean(s) are holding.(对于Prototype类型的Bean必须由使用方自己负责清理资源)
To get the Spring container to release resources held by prototype-scoped beans, try using a custom bean post-processor, which holds a reference to beans that need to be cleaned up(客户端可以借助BeanPostProcessor实现自助清理工作)
5、整体Demo示例和运行日志
@Component
@Slf4j
public class ShutdownHookDemo implements DisposableBean, ApplicationListener<ContextClosedEvent> {
@Override
public void destroy() throws Exception {
log.info("shutdown hook, disposable bean");
}
@Override
public void onApplicationEvent(ContextClosedEvent event) {
log.info("shutdown hook, ContextClosedEvent");
}
@PostConstruct
public void init() {
Runtime.getRuntime().addShutdownHook(new Thread(() -> log.info("shutdown hook, jvm runtime hook")));
}
@PreDestroy
public void preDestroy() {
log.info("shutdown hook, pre destroy");
}
}
2022-05-18 10:13:04.673 INFO 94365 --- [ Thread-6] c.y.s.b.s.demo.ShutdownHookDemo : shutdown hook, jvm runtime hook
2022-05-18 10:13:04.675 INFO 94365 --- [TaskExecutor-24] c.y.s.b.s.demo.ShutdownHookDemo : shutdown hook, ContextClosedEvent
2022-05-18 10:13:04.709 INFO 94365 --- [extShutdownHook] c.y.s.b.s.demo.ShutdownHookDemo : shutdown hook, pre destroy
2022-05-18 10:13:04.709 INFO 94365 --- [extShutdownHook] c.y.s.b.s.demo.ShutdownHookDemo : shutdown hook, disposable bean
Process finished with exit code 143 (interrupted by signal 15: SIGTERM)
特别强调,针对这种hook,如果是kill -9 这种强制杀死进程,是不会触发的,所以对于此类应用应使用kill -15 温柔的关闭应用程序。(kill -9 和kill -15的区别)