2 意外触发 shutdown 方法
类销毁时,也容易写出一堆 bug。
LightService#shutdown,负责关灯:
之前的案例中,若宿管系统重启,灯是不会被关闭的。但随着业务变化,可能会去掉 @Service ,而使用另外一种产生 Bean 的方式:创建一个配置类 BeanConfiguration(标记 @Configuration)来创建一堆 Bean,其中就包含了创建 LightService 类型的 Bean,并将其注册到 Spring 容器:
让 Spring 启动完成后立马关闭当前 Spring 上下文,这就能模拟模拟宿管系统的启停:
以上代码没有其他任何方法的调用,仅是将所有符合约定的类初始化并加载到 Spring 容器,完成后再关闭当前 Spring 容器。
预期:运行后不会有任何log,只改变 Bean 的产生方式。
运行后,控制台打印:
显然 shutdown 方法未按照预期,被执行了,这就导致一个有意思的 bug:
- 在使用新的 Bean 生成方式之前,每一次宿舍管理服务被重启时,宿舍里所有的灯都不会被关闭
- 但修改后,只要服务重启,灯都被意外关闭
你能理解这个bug吗?
源码解析
发现:
- 只有通过使用 Bean 注解注册到 Spring 容器的对象,才会在 Spring 容器被关闭时自动调用 shutdown
- 使用 @Component将当前类自动注入到 Spring 容器时,shutdown 方法则不会被自动执行
可尝试到 Bean 注解类的代码中去寻找一些线索,可看到属性 destroyMethod。
使用 Bean 注解的方法所注册的 Bean 对象,如果用户不设置 destroyMethod 属性,则其属性值为 AbstractBeanDefinition.INFER_METHOD。
此时 Spring 会检查当前 Bean 对象的原始类中是否有名为 shutdown 或 close 的方法:
- 有,此方法会被 Spring 记录下来,并在容器被销毁时自动执行
- 没有,安然无事
查找 INFER_METHOD 枚举值的引用,很容易就找到了使用该枚举值的方法
DisposableBeanAdapter#inferDestroyMethodIfNecessary
private String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) { String destroyMethodName = beanDefinition.getDestroyMethodName(); if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||(destroyMethodName == null && bean instanceof AutoCloseable)) { if (!(bean instanceof DisposableBean)) { try { // 尝试查找 close 方法 return bean.getClass().getMethod(CLOSE_METHOD_NAME).getName(); } catch (NoSuchMethodException ex) { try { // 尝试查找 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); }
代码逻辑和
Bean 注解类中对于 destroyMethod 属性的注释:
完全一致。
- destroyMethodName==INFER_METHOD&&当前类没有实现DisposableBean接口则先查找类的 close 方法:
- 找不到
就在抛出异常后继续查找 shutdown 方法 - 找到
则返回其方法名(close 或者 shutdown)
接着,继续逐级查找引用,最终得到的调用链从上到下为:
- doCreateBean
- registerDisposableBeanIfNecessary
- registerDisposableBean(new DisposableBeanAdapter)
- inferDestroyMethodIfNecessary
然后,我们追溯到了顶层的 doCreateBean:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { // 实例化 bean if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); } // ... // 初始化 bean 实例. Object exposedObject = bean; try { populateBean(beanName, mbd, instanceWrapper); exposedObject = initializeBean(beanName, exposedObject, mbd); } // ... // Register bean as disposable. try { registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); } return exposedObject; }
doCreateBean 管理了Bean的整个生命周期中几乎所有的关键节点,直接负责了 Bean 对象的生老病死,其主要功能包括:
- Bean 实例的创建
- Bean 对象依赖的注入
- 定制类初始化方法的回调
- Disposable 方法的注册
接着,继续查看 registerDisposableBean:
public void registerDisposableBean(String beanName, DisposableBean bean) { synchronized (this.disposableBeans) { this.disposableBeans.put(beanName, bean); } }
DisposableBeanAdapter 类(其属性destroyMethodName 记录了使用哪种 destory 方法)被实例化并添加到 DefaultSingletonBeanRegistry#disposableBeans 属性内,disposableBeans 将暂存这些 DisposableBeanAdapter 实例,直到AnnotationConfigApplicationContext#close被调用。
而当 AnnotationConfigApplicationContext#close被调用时,即当 Spring 容器被销毁时,最终会调用到 DefaultSingletonBeanRegistry#destroySingleton:
- 遍历 disposableBeans 属性
- 逐一获取 DisposableBean
- 依次调用其 close 或 shutdown
public void destroySingleton(String beanName) { // Remove a registered singleton of the given name, if any. removeSingleton(beanName); // Destroy the corresponding DisposableBean instance. DisposableBean disposableBean; synchronized (this.disposableBeans) { disposableBean = (DisposableBean) this.disposableBeans.remove(beanName); } destroyBean(beanName, disposableBean); }
案例调用了 LightService#shutdown 方法,将所有的灯关闭了。
修正
避免在Java类中定义一些带有特殊意义动词的方法来解决。
如果一定要定义名为 close 或者 shutdown 方法,可以将 Bean 注解内 destroyMethod 属性设置为空。如下:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class BeanConfiguration { @Bean(destroyMethod="") public LightService getTransmission() { return new LightService(); } }
为什么 @Service 注入的 LightService,其 shutdown 方法不能被执行?想要执行,则必须要添加 DisposableBeanAdapter,而它的添加是有条件的:
protected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) { AccessControlContext acc = (System.getSecurityManager() != null ? getAccessControlContext() : null); if (!mbd.isPrototype() && requiresDestruction(bean, mbd)) { if (mbd.isSingleton()) { // Register a DisposableBean implementation that performs all destruction // work for the given bean: DestructionAwareBeanPostProcessors, // DisposableBean interface, custom destroy method. registerDisposableBean(beanName, new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc)); } else { //省略非关键代码 } } }
关键的语句在于:
!mbd.isPrototype() && requiresDestruction(bean, mbd)
案例代码修改前后,我们都是单例,所以区别仅在于是否满足requiresDestruction 条件。
DisposableBeanAdapter#hasDestroyMethod: public static boolean hasDestroyMethod(Object bean, RootBeanDefinition beanDefinition) { if (bean instanceof DisposableBean || bean instanceof AutoCloseable) { return true; } String destroyMethodName = beanDefinition.getDestroyMethodName(); if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName)) { return (ClassUtils.hasMethod(bean.getClass(), CLOSE_METHOD_NAME) || ClassUtils.hasMethod(bean.getClass(), SHUTDOWN_METHOD_NAME)); } return StringUtils.hasLength(destroyMethodName); }
- 如果使用 @Service 产生 Bean,则上述代码获取的destroyMethodName是 null
- 使用 @Bean,默认值为AbstractBeanDefinition.INFER_METHOD,参考 Bean 定义:
public @interface Bean { //省略其他非关键代码 String destroyMethod() default AbstractBeanDefinition.INFER_METHOD; }
@Service 标记的 LightService 也没有实现 AutoCloseable、DisposableBean,最终没有添加一个 DisposableBeanAdapter。所以最终我们定义的 shutdown 方法没有被调用。
总结
DefaultListableBeanFactory 类是 Spring Bean 的灵魂,核心就是其doCreateBean,掌控了 Bean 实例的创建、Bean 对象依赖的注入、定制类初始化方法的回调以及 Disposable 方法的注册等关键节点。



