Spring服务退出时,KafkaProducer可以自动关闭的原因探索

315 阅读2分钟

问题

首先,我发现当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继承KafkaProducerclose方法添加打印调用栈的逻辑,然后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);
    }

具体可以参考这两篇博文