聊一聊Spring Bean 的生命周期

2023年 12月 28日 95.9k 0

讲一讲 Spring Bean 的生命周期算是面试时候一道非常经典的问题了!

如果没有研究过 Spring 源码,单纯去背面试题,这个问题也是可以回答出来的,但是单纯的背缺乏理解,而且面试一紧张,就容易背岔了。但是如果你从头到尾看了松哥的 Spring 源码分析,那么这个问题就不需要背了,就根据自己对 Spring 源码的理解讲出来就行了。

在前面的文章中,松哥和大家分析了 Spring 中 Bean 的创建是在 createBean 方法中完成的,在该方法中,真正干活的实际上是 doCreateBean 方法,具体位置在 AbstractAutowireCapableBeanFactory#doCreateBean,小伙伴们在面试时候常被问到的 Spring Bean 的生命周期,实际上就是问 doCreateBean 方法的执行逻辑。

doCreateBean 方法整体上来说,干了四件事:

  • Bean 的实例化。
  • Bean 属性填充。
  • Bean 初始化。
  • Bean 销毁方法注册。
  • 这里大家注意区分实例化和初始化两个方法,实例化是指通过反射创建出来 Bean 实例的过程,而初始化则是调用一些回调函数进行 bean 的一些预处理。

    1. 实例化

    // Instantiate the bean.
    BeanWrapper instanceWrapper = null;
    if (mbd.isSingleton()) {
     instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    }
    if (instanceWrapper == null) {
     instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    Object bean = instanceWrapper.getWrappedInstance();
    Class beanType = instanceWrapper.getWrappedClass();
    if (beanType != NullBean.class) {
     mbd.resolvedTargetType = beanType;
    }

    这段代码的最终目的是为了获取到一个 bean 实例。获取之前先去检查如果有该 bean 尚未完成的 factoryBean 实例就先移除掉。

    createBeanInstance 方法就是大家闭着眼睛也能猜出来的通过反射创建 bean 实例过程,最后我们拿到的 bean 实例就是这个 bean。

    实例化完成之后,还有两个小细节。

    一个是预留了后置处理器修改 BeanDefinition 的接口,在这里可以对 BeanDefinition 进行修改,这块通常用来处理通过注解注入值的情况,这个松哥在之前的文章中也有详细介绍过,小伙伴们参见:一个特殊的 BeanPostProcessor。

    另外一个则是对于循环依赖的处理。

    松哥之前的文章中已经和小伙伴们详细分析了循环依赖的解决思路,参见:如何通过三级缓存解决 Spring 循环依赖。

    这里要做的工作就是根据当前 Bean 的情况,将 Bean 存入到三级缓存中(二级缓存中不存):

    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
      isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
     addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    这块代码的具体含义在之前的文章中松哥都和大家分析过了,这里就不再啰嗦了,感兴趣的小伙伴戳这里:透过源码,捋清楚循环依赖到底是如何解决的!。

    2. 属性填充

    populateBean(beanName, mbd, instanceWrapper);

    这一句就是属性填充的环节了。属性填充就是一个 Bean 中我们通过各种注解如 @Autowired 等注入的对象,@Value 注入的字符串,这些统一都在 populateBean 中进行处理。具体的代码细节松哥在之前的文章中也和大家讲过了:@Autowired 到底是怎么把变量注入进来的?。

    3. 初始化

    exposedObject = initializeBean(beanName, exposedObject, mbd);

    初始化主要是干这样四件事:

    protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
     invokeAwareMethods(beanName, bean);
     Object wrappedBean = bean;
     if (mbd == null || !mbd.isSynthetic()) {
      wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
     }
     try {
      invokeInitMethods(beanName, wrappedBean, mbd);
     }
     catch (Throwable ex) {
      throw new BeanCreationException(
        (mbd != null ? mbd.getResourceDescription() : null), beanName, ex.getMessage(), ex);
     }
     if (mbd == null || !mbd.isSynthetic()) {
      wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
     }
     return wrappedBean;
    }
  • invokeAwareMethods:如果当前 bean 实现了 Aware 接口,那么 Aware 接口相关的方法就在 invokeAwareMethods 方法中被触发。
  • applyBeanPostProcessorsBeforeInitialization:这个是执行 BeanPostProcessor#postProcessBeforeInitialization 方法。
  • invokeInitMethods:这个里边是干两件事,如果我们的 Bean 实现了 InitializingBean 接口,那么该接口中的 afterPropertiesSet 方法就在这里被触发;另一方面就是如果我们通过配置文件 Bean 的初始化方法(XML 文件中的 init-method 属性),那么也会在这里被触发。
  • applyBeanPostProcessorsAfterInitialization:这个是执行 BeanPostProcessor#postProcessAfterInitialization 方法。
  • 这里需要注意的一点是,通过在 XML 文件中配置的 init-method 属性,这个是在第 3 步被触发执行的;但是如果是通过 @PostConstruct 注解标记的 Bean 的初始化方法,则是通过 BeanPostProcessor 来处理的,具体是在 InitDestroyAnnotationBeanPostProcessor#postProcessBeforeInitialization 方法中处理的。这两种看起来作用类似的 Bean 初始化方法,底层处理逻辑并不相同。

    初始化完成之后,还有一个关于循环依赖的处理和判断。

    if (earlySingletonExposure) {
     Object earlySingletonReference = getSingleton(beanName, false);
     if (earlySingletonReference != null) {
      if (exposedObject == bean) {
       exposedObject = earlySingletonReference;
      }
      else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
       String[] dependentBeans = getDependentBeans(beanName);
       Set actualDependentBeans = new LinkedHashSet(dependentBeans.length);
       for (String dependentBean : dependentBeans) {
        if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
         actualDependentBeans.add(dependentBean);
        }
       }
       if (!actualDependentBeans.isEmpty()) {
        throw new BeanCurrentlyInCreationException(beanName,
          "Bean with name '" + beanName + "' has been injected into other beans [" +
          StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
          "] in its raw version as part of a circular reference, but has eventually been " +
          "wrapped. This means that said other beans do not use the final version of the " +
          "bean. This is often the result of over-eager type matching - consider using " +
          "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
       }
      }
     }
    }

    这段代码主要是防止 Spring 容器中创建出来的当前 Bean 和被其他 Bean 所依赖的 Bean 不是同一个。例如有 A 和 B 两个类,Spring 根据既有配置,给 A 生成了代理类,但是 B 引用的并不是 A 的代理对象,而是 A 的原始对象,此时就会有问题。所以这里主要是去判断,确保容器中和被使用的 A 是同一个。

    检查的思路就是先去二级缓存中查找,二级缓存中如果存在,说明这个 Bean 因为循环依赖的原因已经被引用过了(被引用过的 Bean 会存入到二级缓存中),此时去判断 exposedObject 和 bean 是否为同一个 Bean,正常情况下,这两个当然是同一个 Bean,因为 exposedObject 和 bean 指向同一个内存地址。什么情况下,这两个 Bean 会不同呢?如果在 Bean 的后置处理器中,我们使用新的 Bean 替换了旧的 Bean,就会导致最终拿到的 exposedObject 和 bean 两个变量指向的地址不再相同。如果不相同,就要检查当前 Bean 是否有被容器中的其他 Bean 所依赖了,如果有,并且使用了当前 Bean 的 Bean 还正在创建中,那么就赶紧删除掉重新创建,如果使用了当前 Bean 的 Bean 已经创建完成了,那就没办法了,只能抛出异常了。

    4. 销毁

    销毁并不是说要立马把 Bean 给销毁掉,这 Bean 刚创建出来还没使用呢,怎么就给销毁了呢?

    这里的销毁是说把 Bean 的销毁方法先记录下来,将来需要销毁 Bean 或者销毁容器的时候,就调用这些方法去释放 Bean 所持有的资源。

    // Register bean as disposable.
    try {
     registerDisposableBeanIfNecessary(beanName, bean, mbd);

    Bean 的销毁方法我们可以通过注解或者是 XML 文件进行配置。使用注解的话就是 @PreDestroy 注解,被该注解标记的方法可以在 Bean 销毁之前执行,我们可以在该方法中释放资源;也可以使用 XML 文件进行配置 destroy-method="",通过该属性指定 Bean 销毁时候需要执行的方法。另外,当前 Bean 也可以通过实现 DisposableBean 接口,并重写该接口中的 destroy 方法,那么容器销毁的时候,这个方法会被自动调用以释放资源。

    除了这三种常见的方法之外,还有一个办法就是如果当前 Bean 实现了 AutoCloseable 接口,那么当前类中如果存在名为 close 的方法或者名为 shutdown 的方法,那么对应的方法就会被自动调用。

    好啦,大致的流程就是这样了,小伙伴们不妨据此画一个流程图看看。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论