Spring 闯关指南:一个 Bean 的诞生

文章详细描述一个 Bean 诞生的过程,而文章的目录结构,也清晰地反映了整个流程。

Spring 中创建 Bean ,是通过调用 GetBean 方法来触发的。所以,我们会从这个方法开始。这篇文章不会粘贴源码,我会尝试以简明流畅的语言来梳理整个过程。在最后我可能没能达到这个目标,但让你明白我并不是从一开始就有意将文章复杂化也是有意义的。

1. 转换 bean 名称

看见这一步,或许会有所疑惑,但这确实是第一步,因为我们需要一把“钥匙”。这一步是将我们获取 Bean 时所指定的名称,转换为 Spring 容器中所管理 Bean 的真实名称。

例如,在我们通过别名获取 Bean 时, 这一步则会将别名转换为容器中 Bean 真实的名称。

或者,在我们实现 FactoryBean 接口以期待通过工厂获取 Bean 时。在想要获取真正的工厂类而不是所管理的 Bean ,这一步则会去掉前缀 &,因为在容器中存储的工厂 Bean 的真实名称是没有前缀的。但如果不去掉前缀,则获取到的是工厂所管理的 Bean,这在后文有案例。

如果单纯地将容器看作一个键值对的话,这一步就是去获取键的。

2. 从三级缓存中获取

在获取到真实的 beanName 后,会迫切地根据它去单例缓存中获取已经注册过的单例。

这一步指的便是从三级缓存中获取单例。在循环依赖的过程中,这一步能够获取到提前暴露的 Bean 实例。

3. 从父容器中获取

如果在单例缓存中没有获取到,且该容器中不存在 beanName 对应的 BeanDefinition 的话。那么,在父容器存在的情况下,将尝试从父容器中获取。

常见的 SpringMvc ,便会包含父子容器,将不同层次的 Bean 置于不同层次的容器中,也利于管理。

4. 合并 BeanDefinition

如果上述过程都未能成功获取到 Bean 的话,就要考虑去创建 Bean 了。

首先需要准备好 BeanDefinition 。如果一个 BeanDefinition 指定了另一个 BeanDefinition 作为 parent 的话,那么需要合并 BeanDefinition

对应的应用场景是为一个 Bean 指定了 parent 。我们可以把这看作“继承”关系,但这并不是真正意义上的继承关系。我想你能明白我这里描述的是什么。

5. 检查依赖

准备好 BeanDefinition 后,需要做依赖检查。这指的是,如果我们对一个 Bean 使用 @DependsOn 注解来显式表明依赖关系的话,那这一步就能够确保 DependsOn 注解中所指定的 Bean 会先创建。

6. 创建

现在,一切就绪,可以开始创建 Bean 了。

在创建 Bean 之前,补充一个域的概念,这也是 Bean 的特点之一。不同域的 Bean 创建逻辑是否相同呢?

其实,真正创建 Bean 的代码是相同的,只有一份。但基于不同的域在创建 Bean 的前后会有不同的前置和后置逻辑。下面我们将一一分析各域的不同,并将相同的 Bean 的创建过程放在最后一节。

6.1 单例 Bean

单例 Bean 创建前,仍然需要判断缓存。尽管在一开始我们有判断单例的缓存,但你想象这一场景:在创建 A 的过程中,上一步的依赖检查发现 A 显示声明依赖了 B,那么就会触发 B 的创建。但 B 又需要自动装配 A。这时又会触发 A 的创建,并创建成功,在最终回到 A 的创建过程时,依赖检查已经结束,开始进入单例 Bean A 的创建。但此时 A 其实已经被提前创建成功了。

所以,此处仍然需要检查缓存。不过,这里检测的只是一级缓存。若一级缓存singletonObjects 中存在该 Bean ,则直接返回。

6.2 原型 Bean

因为原型每次都会创建对象,所以不会存在从缓存中去获取。但这里仍然需要做循环依赖的检查,并且原型 Bean 间的循环依赖无论如何,都无法解决的。我们可以在脑海中仔细想一想,是不是这个道理。

6.3 其它域 Bean

常见的其它域有 requestsessionapplication 域。这里的 application 在 web 应用中指的便是 servletContext 级别的,也就是在一个 web 应用中有效。request 域仅在一次请求中有效,session 域仅在一次会话中有效。失效后,下次将重新创建。也可以将它们理解为单例,不过它们的单例限制范围更小。

这些域需要尊重它们的范围限制,例如,request 域无法在一个 web 请求外部使用。并且,这些域创建的 Bean 会缓存在它们的范围中,具体的可参考 Scope 接口对应的各实现类,这也是其它域的不同点所在。

这里可能会有一点疑惑,我们经常在 controller 中自动装配 ServletRequest,而 controller 作为单例对象,将在启动时完成创建并初始化。那么,这时注入的 request 是从哪里来的呢?

其实,这是因为 WebApplicationContext 提前注册了可以解析的依赖,然后将 ServletRequest 接口类型映射到 RequestObjectFactory 对象。这样在填充 controller 的属性时,可以发现 ServletRequest 的装配值是 RequestObjectFactory。你可以把这里理解为一个作弊过程,如果正常途径无法帮助我们,那么我们只能寻求 boss 帮助了,类似于这里的 WebApplicationContext

RequestObjectFactory 也不是ServletRequest 类型,而是一个 ObjectFactory 的实现类。所以最终会生成一个 ServletRequest 代理,执行请求则会委托给 RequestObjectFactory对象中返回的 ServletRequest。这个 ServletRequest 就和我们真正的 Web 请求有关了。这种做法很巧妙的将启动时便需要的对象和运行时才产生的对象通过代理关联了起来。

6.4 真正的创建 Bean

前文说过,创建 Bean 的代码是相同的,在这里,我们将统一介绍这一过程。

6.4.1 给 InstantiationAwareBeanPostProcessor 一个机会

首先,会给 InstantiationAwareBeanPostProcessor 一个机会创建 Bean。

	default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
		return null;
	}

若在调用该方法时返回了一个对象,那么会将其用着参数,接着调用 BeanPostProcessorpostProcessAfterInitialization 方法。

	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

最后,此处返回的 Object 将是最终产生的 Bean。
如果最终返回的 Object 不为 null 的话,那么整个创建 Bean 的过程就提前完成,不再执行后面的流程。这是 bean 创建提前结束的机会,也是我们干预 bean 的创建机会。

6.4.2 真实地创建 Bean

现在,开始真实地创建 Bean 。主要过程分别是实例化,填充属性,初始化,销毁注册,下面简要总结这四个过程。

  1. 实例化:
    • BeanDefinition 提供的 Supplier 中返回;
    • 从工厂方法中返回;
    • 反射调用构造函数返回;
      • 自动装配的构造函数调用;
      • 无参构造函数调用;

:需要注意的是,如果这里是创建单例对象,那么在实例化后,属性填充前,需要提前暴露实例化的对象到单例缓存中;

  1. 填充属性:在填充属性前,会给 InstantiationAwareBeanPostProcessor 一个机会来修改 Bean。
  2. 初始化
    • 实现 XXXAware 相关接口的方法回调;
    • 回调 BeanPostProcessor.postProcessBeforeInitialization;
    • 调用初始化方法
      • InitializingBean.afterPropertiesSet
      • init-method
    • 回调 BeanPostProcessor.postProcessAfterInitialization ;

:如果这里是创建单例对象,在完成初始化后,需要检测对象修改状态,这是为了解决循环依赖的。

  1. 销毁注册
    • 检测是否需要销毁: 实现了 DisposableBean,
      AutoCloseable 接口,或者定义的内部方法:close 或者 shutdown, 或者 自定义 DestructionAwareBeanPostProcessor 接口的实现类来决定是否需要消毁。
    • 单例 Bean 的销毁:通过 DefaultSingletonBeanRegistry 中注册 DisposableBeanAdapter,并在合适的时机调用销毁方法即可;
    • prototype 域 Bean 的销毁:不支持,因为 Spring 不管理 prototype 域 Bean 的生命周期,自然也不会去销毁它。
    • request 域 Bean 的销毁: 通过 ServletRequestListener 监听器实现;
    • session 域 Bean 的销毁:通过 HttpSessionBindingListener 监听器实现;
    • application 域 Bean 的销毁:通过 ServletContextListener 监听器实现;

关于如何通过监听器实现,可以通过 Scope 接口的实现类来了解这个过程。

整个 Bean 的创建流程便完了,在这里也只是简述,因为我一开始的目标是不想过于复杂。但现在,先让我们跳出 Bean 的创建流程往下走,因为我们知道还有一些“善后措施”要做。

7. 获取对象

为什么在创建完 Bean 后还要获取对象呢,因为有 FactoryBean 的缘故,这也是我们在前文转换名称一节所提到的。

@Component
public class SessionFactoryBean implements SmartFactoryBean<SessionFactoryBean.Session> {

    @Override
    public Session getObject() throws Exception {
        return new Session("first");
    }

    @Override
    public Class<?> getObjectType() {
        return Session.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    @Override
    public boolean isEagerInit() {
        return true;
    }

    public static class Session{
        private String name;

        protected Session(String name){
            this.name = name;
        }

        public String getName(){
            return name;
        }
    }
}

上述代码实现了 SmartFactoryBean 接口,它是一个 FactoryBean。 我们在通过名称获取对象时:

final Object sessionFactoryBean = annotationConfigApplicationContext.getBean("sessionFactoryBean");

实际上获取到的会是 Session 对象 ,但在容器中管理的 Bean,其实是 SessionFactoryBean 。所以,容器内部在获取到 Bean 后(无论是新创建的还是缓存的),需要在获取对象这一步来计算出你真正想要的对象。当然,如果你要获取的是真正的工厂对象,通过 “&sessionFactoryBean” 名称来获取就好了。

8. 写在最后

现在,看看我们都经历了什么。

首先是找到 Bean 真实的名称,然后再尝试从缓存,从父容器中去获取。如果都没有取到的话,就开始合并 BeanDefinition,检查显式声明的依赖。如果都没有问题的话,就可以开始 Bean 的创建了。无论是什么域的 Bean,其实创建代码都一样,只是前置后置条件不同。而 Bean 的创建又有四个小的过程,也可称之为生命周期:实例化,计算属性,初始化,bean 销毁时的逻辑注册。最后,Bean 无论是 从缓存中获取成功还是创建成功,都需要通过计算获取对象,因为有可能我们获取到的 Bean 是一个 FactoryBean,但我们实际上需要的却是它所管理的对象。

完整的流程就在上面了。需要提醒的是在启动时,只会完成单例 Bean (非延迟初始化)的创建。但如果单例 Bean 有属性需要其它的 Bean,那么又会启动这些相关 Bean 的创建过程。在显式声明依赖检查时,也是如此。如果当前 Bean 有声明依赖其它的 Bean,那么又会启动其它 Bean 的创建过程。

Bean 就是在这样一个循环往复的过程中被创建,并在 Spring 的管理下生生不息的


这是 Spring 闯关指南系列 的第二篇文章,如果你觉得我的文章还不错,并对后续文章感兴趣的话,可以通过扫描下方二维码关注我的公众号。谢谢!
我与风来公众号

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值