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

📅 2026/7/14 13:28:50
深入理解Spring的Bean,从出生到死亡生命周期全过程,深挖底层,直击源码
目录一. Spring 中 Bean 的作用二. Spring 容器中 Bean 底层的创建过程三. 从源码角度来看 DefaultListableBeanFactory四. Spring 中 Bean 的生命周期4.1 Bean 的实例化阶段4.2 Bean 的初始化阶段4.3 Bean 的完成阶段4.4 Bean 的销毁阶段五. 怎样理解从创建出来Bean对象到一个完整Bean对象六. Bean 的初始化阶段都做了哪些事七. Bean 实例的属性填充7.1 注入普通属性7.2 注入单向对象引用属性7.3 注入双向对象属性引用八. 三级缓存实现双向属性注入一. Spring 中 Bean 的作用我们知道Spring 框架为我们提供了IOC容器解决了我们以前 new 对象的这一步骤将对象的控制权交给了 IOC 容器实现了控制反转极大程度上的使项目模块之间耦合度降低。提高了我们的开发效率。但是早期的 Spring 框架需要我们在 xml 配置文件中去配置 Bean 的信息后来 SpringBoot 框架对 Spring 做了封装我们只需要做一个简单的自动装配就可以了其实最底层都是一样的今天我们就来一起揭示 Bean 的神秘面纱。二. Spring 容器中 Bean 底层的创建过程在不使用SpringBoot框架的时候我们定义的 Bean 都会在 xml 配置文件中进行配置如下bean id userService class com.haust.service.UserServiceImplbean而 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 实现类对象。总体过程可以总结为下方一张图三. 从源码角度来看 DefaultListableBeanFactory刚才在 2 中我们可以看到beanDefinitionMap 和 singletonObjects 都在beanFactory中而beanFactory 是 DefaultListableBeanFactory 的类对象我们来看一下这个类的源码。可以看到在该类中有很多的属性这里只看我红线圈出来的可以看到该类中维护着一个叫 beanDefinition 的 Map 对象泛型是StringBeanDefinition这个BeanDefinition 是 GenericBeanDefinition 的父接口。可以作为泛型传入作为约束这样从源码的角度验证了了我们上述结论的。然后我们再找找 singletonObjects 发现没有我们就沿着 DefaultListableBeanFactory 类向上寻找它的父类祖宗类就可以在 DefaultSingletonBeanRegistry 类中发现该类维护着 singletonObjects 属性是一个ConcurrentHashMap 对象初始容量为256容量完全够用即使不够它也会自动进行扩容。我们实际在调用 getBean 方法获取Bean时其实就是从 singletonObjects 中去匹配是否有对应的Bean然后进行返回。四. 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 容器。五. 怎样理解从创建出来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 对象的时候这些属性还没有注入因此会去做一些其他事情。六. Bean 的初始化阶段都做了哪些事这里先说结论Bean 在初始化阶段一共做了以下几件事。1. Bean 实例的属性填充2. Aware 接口属性注入3. BeanPostProcessor 的 before() 方法回调4. InitializingBean 接口的初始化方法回调5. 自定义初始化方法 init 回调6. BeanPostProcessor 的 after() 方法回调上面这几个步骤之中我们最需要关注的就是 Bean 实例的属性填充。七. Bean 实例的属性填充我们在使用 Spring 框架配置 Bean 的时候通常会为其配置属性依赖注入在进行属性注入时会有以下三种情况。7.1 注入普通属性当我们注入普通数据类型如 Stringint 或基本数据类型的集合时底层源码就是利用反射调用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 的创建过程。1xml 配置文件读取到配置的 userService这个 Bean然后底层开始实例化 userService在内存开辟了一处空间存储 userService2实例化 userService 之后开始对 userService 进行初始化和属性填充发现需要注入 userDao然后到容器中下寻找是否有userDao 这个Bean3到容器中发现没有找到 userDao就会暂停 userService 的初始化过程4然后容器去实例化 userDao然后对userDao 进行属性填充成为一个成熟的 Bean 之后就可以再注入到 userService5但是再进行属性填充的时候就会出现问题因为 userDao 也引用了userService所以userDao的初始化过程就会暂停再去创建 userService如此一来就进入了一个死循环userDao 属性注入需要userService去创建userService然后userService 属性注入需要 userDao 在去创建 userDao形成了一个死循环如此一来我们的双向依赖注入根本就不可能达到。因此Spring 框架为了解决这个问题就引入了三级缓存。八. 三级缓存实现双向属性注入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 MapString, Object singletonObjects new ConcurrentHashMap(256); // 2. 早期 Bean 单例池缓存半成品对象且当前对象已经被其他对象引用称之为 “二级缓存” private final MapString, Object earlySingletonObjects new ConcurrentHashMap(16); // 3. 单例 Bean 的工厂池缓存半成品对象对象未被引用使用时再通过工厂创建 Bean称之为“三级缓存” private final MapString, ObjectFactory? singletonFactories new HashMap(16);大家如果仔细看我代码中标注的一级缓存singletonObjects(单例池) 是不是我们上面提到过的存储 Bean的 Map集合它在 Spring 底层是一级缓存用来存储成熟的完整的 Bean 对象。earlySingletonObjects(早期单例池) 是二级缓存它所存储的 Bean 对象是半成品对象而且已经被别的 Bean 对象所引用也就是它已经被依赖注入到其他 Bean 中。singletonFactories 是三级缓存它所存储的 Bean 对象也是半成品对象是刚刚创建没有被其它 Bean 对象依赖注入的 Bean。Spring 容器使用三级缓存循环依赖注入 userDao 和 userService 步骤如下1userService 实例化此时还尚未初始化存储到三级缓存中2userService 属性注入需要 userDao从缓存中获取它是先从一级缓存中获取一级缓存中没有再从二级缓存中获取二级缓存中没有再从三级缓存中获取三级缓存中也没有说明 userDao 这个 Bean 还尚未创建3然后我们去创建 userDaouserDao 实例化存储到三级缓存中这三步如下图所示4userDao 属性注入需要用到 userService先去一级缓存中寻找没有找到再去二级缓存中寻找最后在三级缓存中找到了 userService 半成品然后将 userService 的引用注入到 userDao中5此时因为 三级缓存中的 userService 已经被 userDao 所引用所以我们需要把 userService 从三级缓存中移入二级缓存45两步如下图所示6然后 userDao 完成了属性注入继续执行其他步骤直到成为了一个成熟完整的 Bean然后我们就需要把 userDao 从三级缓存移入 一级缓存不需要经过二级缓存如下图所示7然后我们的 userDao 已经是一个成熟的 Bean 创建完毕此时再回过头继续进行 userService 的初始化过程将 userDao 注入到 userService 中8userDao 注入完成之后在执行其他的步骤成为一个完整的 Bean。然后我们就会把 userService 从二级缓存移入到一级缓存自此userDao 和 userService 的双向依赖注入就算完成也解决了死循环的问题。如下图所示