Spring 源码阅读 28:初始化 Bean 的收尾阶段

 2023-01-09
原文作者:Pseudocode 原文地址:https://juejin.cn/post/7137703805550133261

基于 Spring Framework v5.2.6.RELEASE

接上篇:Spring 源码阅读 27:Bean 实例初始化

前情提要

上一篇分析了 Spring 执行 Bean 实例初始化的源码,包含了感知接口方法的处理 BeanPostProcessor 中处理方法的执行,以及 Bean 的初始化方法的执行。本篇回到doCreateBean方法中,继续分析后续的代码。

依赖检查

接着看后续的代码。

    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<String> 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.");
             }
          }
       }
    }

这里的判断条件earlySingletonExposure默认为true,因此会进入最外层if语句块,进入之后,会再次尝试通过getSingleton方法从缓存中获取 Bean 实例。之前,Spring 已经将创建好的 Bean 实例添加到了第三级缓存也就是singletonFactories中,但是这里调用getSingleton方法时,传入的allowEarlyReference的值为true,也就是不允许从工厂缓存中获取,因此,第一次执行到这里的时候,返回的是null,此时,下一层的if语句块中的内容不会被执行。

假设,在 Spring 创建完早期的 Bean 实例,并将其添加到工厂缓存中之后,有其他的地方,通过缓存获取到了早期的 Bean 实例,那么这里是可以获取到earlySingletonReference的。我们试着分析一下,在这种情况下,Spring 会怎么处理。

首先,判断了exposedObject == bean,这两个对象分别是执行populateBeaninitializeBean两个方法之后和之前的 Bean 实例引用。如果它俩还是同一个对象的话,就把earlySingletonReference赋值给exposedObjectexposedObject是最终方法要返回的 Bean 实例对象。

如果它俩已经不是一个对象了,那么在执行else if 语句块中的逻辑。这里判断了在容器不允许循环引用时注入原始 Bean 实例的情况下是否有其他的 Bean 依赖了当前的 Bean,如果有的话,执行后续的检查。找到所有依赖当前 Bean 的 Bean 的名称,通过调用removeSingletonIfCreatedForTypeCheckOnly方法,将其中不在alreadyCreated集合中的beanName对应的 Bean 从容器中移除,这部分是指用于类型检查的 Bean。如果其中有alreadyCreated集合中包含的 Bean,则将其添加到actualDependentBeans集合中。最后,如果actualDependentBeans不为空,则抛出异常。异常信息大意是说,当前的 Bean 已经作为循环引用的一部分以原始的版本注入了其他的 Bean 中,但已经被包装,意味着那些 Bean 使用的不是当前 Bean 的最终版本。

注册 DisposableBean

接下来是最终的 Bean 实例被返回之前的最后一段代码。

    // Register bean as disposable.
    try {
       registerDisposableBeanIfNecessary(beanName, bean, mbd);
    }
    catch (BeanDefinitionValidationException ex) {
       throw new BeanCreationException(
             mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
    }

根据方法名称,这里注册了 DisposableBean,目前我们还不知道 DisposableBean 是什么,进入方法看看源码。

    // org.springframework.beans.factory.support.AbstractBeanFactory#registerDisposableBeanIfNecessary
    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 {
             // A bean with a custom scope...
             Scope scope = this.scopes.get(mbd.getScope());
             if (scope == null) {
                throw new IllegalStateException("No Scope registered for scope name '" + mbd.getScope() + "'");
             }
             scope.registerDestructionCallback(beanName,
                   new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc));
          }
       }
    }

这里根据当前的 Bean 是不是单例 Bean 分别做了处理,如果是单例 Bean 的话,执行了如下操作:

    registerDisposableBean(beanName,
                   new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessors(), acc));

进入registerDisposableBean方法:

    public void registerDisposableBean(String beanName, DisposableBean bean) {
       synchronized (this.disposableBeans) {
          this.disposableBeans.put(beanName, bean);
       }
    }

这里只是把beanName和调用方法时创建的 DisposableBeanAdapter 添加到了容器的disposableBeans集合中。我们再看看 DisposableBeanAdapter 到底是什么。

DisposableBeanAdapter

202301012007242381.png

它是 DisposableBean 接口的一个实现类,DisposableBean 的接口定义如下:

    public interface DisposableBean {
    
    void destroy() throws Exception;
    
    }

这里只有一个destroy方法。DisposableBean 其实是一个跟上一篇中介绍的 InitializingBean 很类似的接口。InitializingBean 中定义了 Bean 初始化时调用的方法afterPropertiesSet,而 DisposableBean 中的destroy方法则是 Bean 实例被销毁的时候调用的。也就是说,如果一个 Bean 想要在自己被销毁时执行一些自定义的逻辑,就可以通过实现 DisposableBean 接口,在destroy方法中实现这些逻辑。

同样的,即使不实现 DisposableBean 接口,也可以跟 InitializingBean 对应的init-method属性一样,在 XML 中指定一个实例销毁时执行的方法名称。配置方式如下:

    <bean id="user" class="xxx.User" destroy-method="destroy" />

DisposableBeanAdapter 类实现了 DisposableBean 接口,那它肯定包含了销毁 Bean 实例的逻辑。这里 Spring 通过调用registerDisposableBean方法,为每一个刚刚初始化完的 Bean 都注册了销毁的逻辑。注意,这里只是注册,这段逻辑只会在对象被销毁的时候,由 Spring 调用destroy方法执行。

Spring 为 Bean 实例注册的销毁逻辑

接下来,找到 DisposableBeanAdapter 的destroy方法,看看这里具体为每个 Bean 添加了那些注销的逻辑。

    // org.springframework.beans.factory.support.DisposableBeanAdapter#destroy
    @Override
    public void destroy() {
       if (!CollectionUtils.isEmpty(this.beanPostProcessors)) {
          for (DestructionAwareBeanPostProcessor processor : this.beanPostProcessors) {
             processor.postProcessBeforeDestruction(this.bean, this.beanName);
          }
       }
    
       if (this.invokeDisposableBean) {
          if (logger.isTraceEnabled()) {
             logger.trace("Invoking destroy() on bean with name '" + this.beanName + "'");
          }
          try {
             if (System.getSecurityManager() != null) {
                AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
                   ((DisposableBean) this.bean).destroy();
                   return null;
                }, this.acc);
             }
             else {
                ((DisposableBean) this.bean).destroy();
             }
          }
          catch (Throwable ex) {
             String msg = "Invocation of destroy method failed on bean with name '" + this.beanName + "'";
             if (logger.isDebugEnabled()) {
                logger.warn(msg, ex);
             }
             else {
                logger.warn(msg + ": " + ex);
             }
          }
       }
    
       if (this.destroyMethod != null) {
          invokeCustomDestroyMethod(this.destroyMethod);
       }
       else if (this.destroyMethodName != null) {
          Method methodToInvoke = determineDestroyMethod(this.destroyMethodName);
          if (methodToInvoke != null) {
             invokeCustomDestroyMethod(ClassUtils.getInterfaceMethodIfPossible(methodToInvoke));
          }
       }
    }

方法中用到的一些成员变量,是在创建 DisposableBeanAdapter 时,构造方法中初始化的。这个方法算是 Spring 给每个 Bean 提供的销毁逻辑的实现,代码不少,但是仔细看一下关键的逻辑并不多:

首先,找到 Bean 对应的 DestructionAwareBeanPostProcessor 后处理器,执行其postProcessBeforeDestruction方法。后处理器见过很多了,这里就不再详细介绍了,它也是 BeanPostProcessor 的子接口,这个方法在执行销毁逻辑之前调用,可以看作是 Spring 给我们留了一个执行销毁逻辑之前的扩展点。

然后,判断invokeDisposableBean成员变量的值,这个成员变量是在构造方法中初始化的,我们看一下它的值是怎么来的。

    this.invokeDisposableBean =
          (this.bean instanceof DisposableBean && !beanDefinition.isExternallyManagedDestroyMethod("destroy"));

这里其实跟调用初始化方法时的判断逻辑是一样的,回到destroy方法中,如果当前的 Bean 实现了 DisposableBean 接口,并且它的destroy方法不是外部管理的,那么就调用它的destroy方法。

最后一部分,调用invokeCustomDestroyMethod方法的逻辑,跟调用自定义初始化方法的逻辑也类似,如果配置了自定义的销毁方法(也就是bean标签的destroy-method属性),则执行。

总结

至此,用了 10 篇文章,从getBean方法开始,Spring 创建和初始化 Bean 实例对象的过程就分析完了。