深入理解Spring的Bean,从出生到死亡生命周期全过程,深挖底层,直击源码

目录

1. Spring 中 Bean 的作用

2. Spring 容器中 Bean 底层的创建过程

3. 从源码角度来看 DefaultListableBeanFactory

4. Spring 中 Bean 的生命周期

4.1 Bean 的实例化阶段

4.2 Bean 的初始化阶段

4.3 Bean 的完成阶段

4.4 Bean 的销毁阶段

5. 怎样理解从创建出来Bean对象到一个完整Bean对象?

6. Bean 的初始化阶段都做了哪些事?

7. Bean 实例的属性填充

7.1 注入普通属性

7.2 注入单向对象引用属性

7.3 注入双向对象属性引用

8. 三级缓存实现双向属性注入


1. Spring 中 Bean 的作用

我们知道,Spring 框架为我们提供了IOC容器,解决了我们以前 new 对象的这一步骤,将对象的控制权交给了 IOC 容器,实现了控制反转,极大程度上的使项目模块之间耦合度降低。提高了我们的开发效率。

但是早期的 Spring 框架需要我们在 xml 配置文件中去配置 Bean 的信息,后来 SpringBoot 框架对 Spring 做了封装,我们只需要做一个简单的自动装配就可以了,其实最底层都是一样的,今天我们就来一起揭示 Bean 的神秘面纱。

2. Spring 容器中 Bean 底层的创建过程

在不使用SpringBoot框架的时候,我们定义的 Bean 都会在 xml 配置文件中进行配置,如下

<bean id = "userService" class = "com.haust.service.UserServiceImpl"><bean>

而 Spring 容器在进行初始化的时候,会将 xml 文件中配置的 <bean> 的信息封装成一个对象,注意它不是将 UserServiceImpl 这个类的对象进行封装,而是将整个 <bean> 标签信息封装为一个对象,这个对象在底层是 GenericBeanDefinition 的对象,GenericBeanDefinition 是我们 Spring 框架中的一个类,它的对象专门用来存放 Bean 标签的信息。有点类似于我们开发时写的实体类,这样解释各位会更清楚一些。

而且,所有的 GenericBeanDefinition 这个类的对象会存储到一个 beanDefinitionMap 集合中去,以KV键值对的形式存储,key就是 bean 的id,值就是 bean 的属性信息。

然后,Spring 框架会对这个 Map 集合进行遍历,取出里面存储的一个个 Bean 对象的信息,然后利用反射创建对象,创建后的对象是 Object 对象,然后会存到 singletonObjects 的 Map 集合中去。

singletonObjects 翻译过来也叫单例池,它与 GenericBeanDefinition都是维护在 BeanFactory 中。当调用 getBean 方法时,会从这个 Map 中取出Bean实例对象返回。

总体创建过程大致可以概括为以下几步,这里先记住大致步骤,下面我们会逐步分析,深入底层。

第一步:读取 xml 配置文件的 <bean> 并封装为 GenericBeanDefinition 对象;

第二步:存储 GenericBeanDefinition 对象到 beanDefinitionMap 集合中;

第三步:遍历集合,通过反射创建对象;

第四步:将创建的对象放到 singletonObjects 的 Map 集合中;

第五步:调用 getBean 方法即可获取到创建的 Bean。

如下图片

xml 文件如下,这里简单举例,定义了 userDao和userService两个Bean,如下图

main 方法创建容器并获取Bean,如下图

我们打断点运行,找到 BeanFactory ,可以发现里面有 singletonObjects和 beanDefinitionMap。这里我们先来看 beanDifinitionMap 。

beanDefinitionMap 是一个 ConcurrentHashMap ,了解过它的底层原理的应该知道,Java底层的ConcurrentHashMap 名字就叫 table,我们点开table,可以在这个 table 数组中看到我们定义的两个 bean,分别是userService和userDao。

但各位仔细观察,可以发现它存储的是一个KV键值对,键是Bean的名称即 我们写<bean>标签时bean的 id,值各位仔细看,是一个GenericBeanDefinition,存储的是对应的 bean 对象信息。

接着刚才的,我说了,实际的 Bean 对象定义在 singletonObjects 中,我们点开上面的 singletonObjects 看一看,如下图

我们可以得知,singletonObjects 它本身也是一个 ConcurrentHashMap,我们点开 table,就可以在 table 中找到 userService 和 userDao,这个时候你会发现,此时该键值对对象存储的值才是真正的 Bean 实现类对象。

总体过程可以总结为下方一张图

3. 从源码角度来看 DefaultListableBeanFactory

刚才在 2 中,我们可以看到,beanDefinitionMap 和 singletonObjects 都在beanFactory中,而beanFactory 是 DefaultListableBeanFactory 的类对象,我们来看一下这个类的源码。

可以看到,在该类中有很多的属性,这里只看我红线圈出来的,可以看到该类中维护着一个叫 beanDefinition 的 Map 对象,泛型是<String,BeanDefinition>,这个BeanDefinition 是 GenericBeanDefinition 的父接口。可以作为泛型传入作为约束,这样从源码的角度验证了了我们上述结论的。

然后我们再找找 singletonObjects ,发现没有,我们就沿着 DefaultListableBeanFactory 类向上寻找它的父类祖宗类,就可以在 DefaultSingletonBeanRegistry 类中发现该类维护着 singletonObjects 属性,是一个ConcurrentHashMap 对象,初始容量为256,容量完全够用,即使不够它也会自动进行扩容。

我们实际在调用 getBean 方法获取Bean时,其实就是从 singletonObjects 中去匹配是否有对应的Bean,然后进行返回。

4. Spring 中 Bean 的生命周期

刚才我只是简单的阐述了Spring 容器中 Bean 底层的创建过程,其实底层真实的过程是非常非常复杂的,需要经过一系列的步骤,我们也把这整个阶段称为 Spring Bean 的生命周期。

Spring Bean 的生命周期是从 Bean 实例化之后,即通过反射创建出对象之后,到 Bean 成为一个完整对象,最终存储到单例池中。再加上最终的销毁阶段,这个过程大体上分为以下四个阶段,其中最重要也是最复杂的就是 Bean 的初始化阶段,该阶段也是面试时常常会问到的一个重要知识点。下面我们会对其进行详细分析。

4.1 Bean 的实例化阶段

Spring 框架会取出 BeanDefinition 的信息进行判断。

判断当前 Bean 的范围是否是 singleton 单例的?如果是单例的就创建对象,如果是 prototype (原型的,多实例的),就在 getBean 时再去创建对象;

判断是否是延迟加载的?如果是延迟加载也不会马上创建对象,等到getBean时再创建对象;

是否不是 BeanFactory 的,如果是就用工厂Bean的方式创建对象;

还有一些列步骤这里就不一一列举了,最终将一个普通的 singleton 的 Bean 通过反射进行实例化。

4.2 Bean 的初始化阶段

Bean 的初始化阶段,Bean 创建之后还仅仅是一个半成品,还需要对 Bean 实例的属性进行填充,依赖注入等,还要执行一些 Aware 接口方法,执行 BeanPostProcessor 方法,执行 InitizlizingBean 接口的初始化方法,执行自定义的 init 初始化方法等,往往我们对 Bean 功能的增强都是在这个初始化阶段去完成的。

4.3 Bean 的完成阶段

经过了初始化阶段,Bean 就成为了一个完整的 Bean ,被存储到单例池 singletonObjects 中去了,即完成了 Spring Bean 的整个创建周期。

4.4 Bean 的销毁阶段

Spring 容器关闭之后,内部的 Bean 就会被销毁,这一步其实也可以算是属于是生命周期的最后一部分。不作为重点,了解即可,而且我们也很少会去关闭 Spring 容器。

5. 怎样理解从创建出来Bean对象到一个完整Bean对象?

也许很多同学会有一个疑问。在实例化阶段,我Spring 都已经创建出来 Bean 对象了,怎么还说从创建bean 对象到一个完整对象呢?其实从上面我列举的四个步骤各位也可以发现,在4.1 Bean 的实例化阶段到 4.3 存储到 singletonObvjects Map 集合中,我们中间还需要执行 bean 的一个初始化阶段。

我给大家举个例子。

依赖注入也是 Spring 容器的一个特点,如下所示,我的 UserDao 需要注入到 UserService 中。

// 创建 UserDao 的 Bean 对象
<bean id = userDao class = "com.haust.dao.UserDao"></bean>

// 创建 UserService 的bean对象,并依赖注入userDao
<bean id = userService clas = "com.haust.service.UserService">
    <property name = "userDao" ref = "userDao"></property>
</bean>

我们也知道了,Spring 容器在创建 Bean 的时候,会把 bean 的信息一起封装到 BeanDefinition 中,然后通过反射创建对象,这里我们会创建 userDao和userService,实际上创建出来 UserService 时,因为它内部还有 userDao 属性尚未注入,它其实只是一个半成品。只有执行了依赖注入将 userDao 注入到 userService 之后,它就会成为一个完整的 Bean。当然我这里说的很片面,其底层在初始化的过程中还需要执行其它的方法,这也曾侧面印证了一个结论,Bean 在经过实例化之后,并不是马上变成了一个完整的 Bean,还需要经过一步步的加工,这就是创建一个 Bean 对象到成为一个完整的 Bean 这句话的意思,有些 Bean 我们会为其设置一些属性,底层在创建了 Bean 对象的时候,这些属性还没有注入,因此会去做一些其他事情。

6. Bean 的初始化阶段都做了哪些事?

这里先说结论,Bean 在初始化阶段一共做了以下几件事。

1. Bean 实例的属性填充;

2. Aware 接口属性注入;

3. BeanPostProcessor 的 before() 方法回调;

4. InitializingBean 接口的初始化方法回调;

5. 自定义初始化方法 init 回调;

6. BeanPostProcessor 的 after() 方法回调;

上面这几个步骤之中,我们最需要关注的就是 Bean 实例的属性填充。

7. Bean 实例的属性填充

我们在使用 Spring 框架配置 Bean 的时候,通常会为其配置属性依赖注入,在进行属性注入时,会有以下三种情况。

7.1 注入普通属性

当我们注入普通数据类型,如 String,int 或基本数据类型的集合时,底层源码就是利用反射调用Set方法将属性直接设置进去。这种情况不难,只是底层利用了反射调用Set方法,所以我这里就不展开说了。

7.2 注入单向对象引用属性

就是我们常说的依赖注入,例如 userDao 依赖注入到 userService 中,但是 userDao 没有设置userService。如下图所示,这里的注入是单向的,userDao 单向注入到 userService 中。

这种情况底层再进行注入时,userService 会调用 getBean 方法获取到 userDao,然后通过反射调用Set方法将 userDao 设置到 userService 中去。因为 Bean 的创建没有先后顺序,如果此时容器中没有 userDao,那么底层就会先暂停 userService 的生命周期,先去创建 userDao。然后将 userDao 注入到 userService 中,继续 userService 的生命周期。

我们来简单做一个小案例

如下,我在UseDaoImpl 这个类中在无参构造方法中加上一句话

然后我在 UserServiceImpl 这个类的无参构造方法中也加上一句话,在 setDao 的方法中也加上一句话来做标记

第一种情况,在 XML 配置文件中,我把 userDao 这个Bean 配置在 userService 的上面让userDao 先被创建,我们到启动类中去执行main方法,在控制台中得到如下结果

可以看到,程序执行完毕,我们在xml文件中将 userDao 配置在上面,所以 userDao 先创建,然后 userService 后创建,然后 userDao 被注入到 userService 中。

第二种情况,在 XML 配置文件中,我把 userDao 这个Bean 配置在 userService 的下面让userService 先被创建,我们执行main方法,在控制台中得到如下结果

在控制台中得到,userService 先被创建,但创建之后又去创建了 userDao ,然后再去注入userDao。

这就是我刚才所说的,如果容器中有的 Bean 需要其他 Bean 或者属性的注入,底层会直接调用 getBean 方法获取到相应的 Bean 进行注入即可,如果没有相应的 Bean ,则会先去创建要注入的 Bean 。 

7.3 注入双向对象属性引用

这种情况是最为复杂的一种,但在实际开发使用的较少,在面试时可能会问到。

双向注入,即 userDao 注入到 userService 中,然后 userService 又注入到 userDao 中,配置的 Bean 和双向引用图如下所示。

我们的 Spring 容器在去创建 userDao 和 userService 时,按照原本的流程(单向对象引用属性)应该是下面这张图的样子,注意,我说的是原本的流程,因为双向注入实际上流程并不是下图这个样子的,但我们要知道,按照原本的注入,会产生什么样的后果

结合上面的两幅图,我来给大家梳理一下底层 Bean 的创建过程。

(1)xml 配置文件读取到配置的 userService这个 Bean,然后底层开始实例化 userService,在内存开辟了一处空间存储 userService;

(2)实例化 userService 之后,开始对 userService 进行初始化和属性填充,发现需要注入 userDao,然后到容器中下寻找是否有userDao 这个Bean;

(3)到容器中发现没有找到 userDao,就会暂停 userService 的初始化过程;

(4)然后容器去实例化 userDao,然后对userDao 进行属性填充,成为一个成熟的 Bean 之后就可以再注入到 userService;

(5)但是,再进行属性填充的时候,就会出现问题,因为 userDao 也引用了userService,所以userDao的初始化过程就会暂停,再去创建 userService,如此一来,就进入了一个死循环,userDao 属性注入需要userService,去创建userService,然后userService 属性注入需要 userDao ,在去创建 userDao,形成了一个死循环,如此一来,我们的双向依赖注入根本就不可能达到。

因此,Spring 框架为了解决这个问题,就引入了三级缓存。

8. 三级缓存实现双向属性注入

Spring 使用三级缓存 存储成熟的 Bean 实例和半成品的 Bean 实例用于解决循环引用以来问题,也就是刚才我所说到的死循环问题。

这里我截取了 Spring 框架中 DefaultSingletonBeanRegistry 这个类的一小段源码,这个类里面维护了三个 Map 集合,我给它们做了注释。

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    private static final int SUPPRESSED_EXCEPTIONS_LIMIT = 100;
// 1.最终存储单例 Bean 成品的容器,即初始化和实例化都完成已经是完整的 Bean,称之为 “一级缓存”
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
// 2. 早期 Bean 单例池,缓存半成品对象,且当前对象已经被其他对象引用,称之为 “二级缓存”
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
// 3. 单例 Bean 的工厂池,缓存半成品对象,对象未被引用,使用时再通过工厂创建 Bean,称之为“三级缓存”
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);

大家如果仔细看我代码中标注的一级缓存,singletonObjects(单例池) 是不是我们上面提到过的存储 Bean的 Map集合,它在 Spring 底层是一级缓存,用来存储成熟的完整的 Bean 对象。

earlySingletonObjects(早期单例池) 是二级缓存,它所存储的 Bean 对象是半成品对象,而且已经被别的 Bean 对象所引用,也就是它已经被依赖注入到其他 Bean 中。

singletonFactories 是三级缓存,它所存储的 Bean 对象也是半成品对象,是刚刚创建,没有被其它 Bean 对象依赖注入的 Bean。

Spring 容器使用三级缓存循环依赖注入 userDao 和 userService 步骤如下

(1)userService 实例化,此时还尚未初始化,存储到三级缓存中;

(2)userService 属性注入,需要 userDao,从缓存中获取,它是先从一级缓存中获取,一级缓存中没有,再从二级缓存中获取,二级缓存中没有,再从三级缓存中获取,三级缓存中也没有,说明 userDao 这个 Bean 还尚未创建;

(3)然后我们去创建 userDao,userDao 实例化,存储到三级缓存中;这三步如下图所示

(4)userDao 属性注入,需要用到 userService,先去一级缓存中寻找,没有找到再去二级缓存中寻找,最后在三级缓存中找到了 userService 半成品,然后将 userService 的引用注入到 userDao中;

(5)此时因为 三级缓存中的 userService 已经被 userDao 所引用,所以我们需要把 userService 从三级缓存中移入二级缓存;45两步如下图所示

(6)然后 userDao 完成了属性注入,继续执行其他步骤,直到成为了一个成熟完整的 Bean,然后,我们就需要把 userDao 从三级缓存移入 一级缓存,不需要经过二级缓存;如下图所示

(7)然后,我们的 userDao 已经是一个成熟的 Bean ,创建完毕,此时再回过头继续进行 userService 的初始化过程,将 userDao 注入到 userService 中;

(8)userDao 注入完成之后,在执行其他的步骤成为一个完整的 Bean。然后,我们就会把 userService 从二级缓存移入到一级缓存,自此,userDao 和 userService 的双向依赖注入就算完成,也解决了死循环的问题。如下图所示

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值