Spring Bean生命周期你除了会背八股文面试,真的会用了吗?(下)

简介: Spring Bean生命周期你除了会背八股文面试,真的会用了吗?

2 意外触发 shutdown 方法

类销毁时,也容易写出一堆 bug。

LightService#shutdown,负责关灯:

image.png

之前的案例中,若宿管系统重启,灯是不会被关闭的。但随着业务变化,可能会去掉 @Service ,而使用另外一种产生 Bean 的方式:创建一个配置类 BeanConfiguration(标记 @Configuration)来创建一堆 Bean,其中就包含了创建 LightService 类型的 Bean,并将其注册到 Spring 容器:


image.png

让 Spring 启动完成后立马关闭当前 Spring 上下文,这就能模拟模拟宿管系统的启停:

image.png

以上代码没有其他任何方法的调用,仅是将所有符合约定的类初始化并加载到 Spring 容器,完成后再关闭当前 Spring 容器。

预期:运行后不会有任何log,只改变 Bean 的产生方式。

运行后,控制台打印:

image.png

显然 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 方法的注册等关键节点。

目录
相关文章
|
10月前
|
XML Java 测试技术
Spring IOC—基于注解配置和管理Bean 万字详解(通俗易懂)
Spring 第三节 IOC——基于注解配置和管理Bean 万字详解!
774 26
|
6月前
|
设计模式 算法 架构师
京东二面:说下spring中常用的设计模式? (一个 深入骨髓的答案, 面试官跪下了)
京东二面:说下spring中常用的设计模式? (一个 深入骨髓的答案, 面试官跪下了)
京东二面:说下spring中常用的设计模式? (一个 深入骨髓的答案, 面试官跪下了)
|
11月前
|
前端开发 安全 Java
2025春招,Spring 面试题汇总
本文详细整理了2025年春招必备的Spring面试题,分为基础和高级两大部分,帮助求职者全面掌握Spring相关知识点,结合实际项目经验,提升面试成功率。内容涉及Spring框架、AOP、事务管理、数据库集成、Spring Boot、Spring Security、微服务架构等,助力你在春招中脱颖而出。
2481 0
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
381 2
|
存储 Java Spring
【Spring】获取Bean对象需要哪些注解
@Conntroller,@Service,@Repository,@Component,@Configuration,关于Bean对象的五个常用注解
364 12
|
存储 Java 应用服务中间件
【Spring】IoC和DI,控制反转,Bean对象的获取方式
IoC,DI,控制反转容器,Bean的基本常识,类注解@Controller,获取Bean对象的常用三种方式
507 12
|
XML Java 数据格式
Spring容器Bean之XML配置方式
通过对以上内容的掌握,开发人员可以灵活地使用Spring的XML配置方式来管理应用程序的Bean,提高代码的模块化和可维护性。
394 6
|
XML Java 数据格式
🌱 深入Spring的心脏:Bean配置的艺术与实践 🌟
本文深入探讨了Spring框架中Bean配置的奥秘,从基本概念到XML配置文件的使用,再到静态工厂方式实例化Bean的详细步骤,通过实际代码示例帮助读者更好地理解和应用Spring的Bean配置。希望对你的Spring开发之旅有所助益。
495 4
|
Java 关系型数据库 数据库
京东面试:聊聊Spring事务?Spring事务的10种失效场景?加入型传播和嵌套型传播有什么区别?
45岁老架构师尼恩分享了Spring事务的核心知识点,包括事务的两种管理方式(编程式和声明式)、@Transactional注解的五大属性(transactionManager、propagation、isolation、timeout、readOnly、rollbackFor)、事务的七种传播行为、事务隔离级别及其与数据库隔离级别的关系,以及Spring事务的10种失效场景。尼恩还强调了面试中如何给出高质量答案,推荐阅读《尼恩Java面试宝典PDF》以提升面试表现。更多技术资料可在公众号【技术自由圈】获取。