问题
首先,我发现当SpringBoot服务退出时,KafkaProducer可以自动关闭。但是为什么会自动关闭,这个流程是怎样调用的,我并不清楚。
因此我记录一下自己的解决过程。前后总共用了三种方法去探索,前两种失败,最后一种能较好的查到原因,而且有较好的通用性。
Round1:尝试Arthas。失败
通过命令 watch org.apache.kafka.clients.producer.KafkaProducer close -b 去观察我的服务。
但是,只有当SpringBoot服务关闭时,KafkaProducer的close方法才会被调用起来,而arthas也会一样关闭掉,并不能观察到close方法的调用栈。
Round2:尝试添加BeanPostProcessor。失败
添加了一个MyBeanPostProcessor,但仍然看不到何时关闭。
@Component
public class MyBeanPostProcessor implements DestructionAwareBeanPostProcessor {
private static final Logger log = LoggerFactory.getLogger(MyBeanPostProcessor.class);
@Override
public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
if (beanName.equals("kafkaProducer"))
log.info("<<<<<< 销毁之前执行, 如 @PreDestroy");
}
}
Round3: 尝试继承KafkaProducer,并覆写close方法,添加打印调用栈的逻辑。成功。
使用MyKafkaProducer继承KafkaProducer,close方法添加打印调用栈的逻辑,然后Bean对象指定为MyKafkaProducer。
public class MyKafkaProducer<K, V> extends KafkaProducer<K, V> {
public MyKafkaProducer(Properties properties) {
super(properties);
}
@Override
public void close() {
printStackTrace();
super.close();
}
public static void printStackTrace() {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for (StackTraceElement element : stackTrace) {
System.out.println(element.toString());
}
}
}
之后关闭SpringBoot应用时,就可以看到调用堆栈了
java.lang.Thread.getStackTrace(Thread.java:1564)
com.wd.demo.config.MyKafkaProducer.printStackTrace(MyKafkaProducer.java:24)
com.wd.demo.config.MyKafkaProducer.close(MyKafkaProducer.java:19)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:498)
org.springframework.beans.factory.support.DisposableBeanAdapter.invokeCustomDestroyMethod(DisposableBeanAdapter.java:339)
org.springframework.beans.factory.support.DisposableBeanAdapter.destroy(DisposableBeanAdapter.java:273)
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroyBean(DefaultSingletonBeanRegistry.java:571)
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingleton(DefaultSingletonBeanRegistry.java:543)
org.springframework.beans.factory.support.DefaultListableBeanFactory.destroySingleton(DefaultListableBeanFactory.java:1036)
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingletons(DefaultSingletonBeanRegistry.java:504)
org.springframework.beans.factory.support.DefaultListableBeanFactory.destroySingletons(DefaultListableBeanFactory.java:1029)
org.springframework.context.support.AbstractApplicationContext.destroyBeans(AbstractApplicationContext.java:1057)
org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:1026)
org.springframework.context.support.AbstractApplicationContext$1.run(AbstractApplicationContext.java:945)
根据上面堆栈信息就可以知道,Spring通过ConfigurableApplicationContext类中的registerShutdownHook方法去close Bean,这个方法会在运行时中添加一个jvm关闭的钩子函数。
然后,会通过inferDestroyMethodIfNecessary方法,尝试推断出Bean对象的destroyMethod,一般就是close或者shutdown。
最后,通过反射调用这个方法,就可以了。
源码如下。
@Nullable
private String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {
String destroyMethodName = beanDefinition.getDestroyMethodName();
// 如果destroyMethodName是INFER_METHOD,那么推断出真正的destroyMethodName
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||
(destroyMethodName == null && bean instanceof AutoCloseable)) {
// Only perform destroy method inference or Closeable detection
// in case of the bean not explicitly implementing DisposableBean
if (!(bean instanceof DisposableBean)) {
try {
// 获取close方法
return bean.getClass().getMethod(CLOSE_METHOD_NAME).getName();
}
catch (NoSuchMethodException ex) {
try {
// 没有close方法,则获取shutdown方法
return bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName();
}
catch (NoSuchMethodException ex2) {
// no candidate destroy method found
}
}
}
return null;
}
return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null);
}
具体可以参考这两篇博文