经典题解析:Spring Bean 从定义到可用,经历了哪些步骤

图中先处理 Bean 的定义,再创建对象,初始化之后还可能交付一个代理。

Spring 帮你创建对象,但一个 Bean 从配置出现到能够被业务使用,远不止调用一次构造器。答生命周期题,最容易出错的是把“修改定义”“创建实例”和“初始化后包装”混成同一段流程,最后说不清扩展点到底作用于谁。

定义是说明书,不是对象

容器从注解、配置类或其他配置方式获取 Bean 定义。定义描述类型、作用域、依赖、初始化方式等信息。此时可以已经知道如何创建对象,却还没有创建相应业务实例;懒加载或不同作用域也会影响真正创建的时机。

BeanFactoryPostProcessor 一类扩展点主要处理容器中的定义或配置元数据。它与对实例执行回调的 BeanPostProcessor 不同。若为了检查定义就提前获取业务 Bean,可能使对象过早创建,影响后续处理链,因此不能把这两类扩展点随意互换。

描述顺序时还应限定对象种类:下面以常规容器管理的 Bean 创建为主线,不把工厂对象、特殊基础设施实例和各种提前代理路径全部画成一条绝对固定流水线。

实例化与依赖注入相互衔接

实例化阶段需要选择创建方式,例如构造器或工厂方法。使用构造器注入时,依赖参数必须在调用构造器前准备好;使用属性注入时,则会在对象创建之后设置相关属性。所以“所有依赖注入都发生在构造器之后”并不准确。

接下来容器完成相应属性填充,实例才逐渐具备业务运行所需的协作者。构造完成并不自动代表初始化回调已经执行,也不代表对象可以安全地向外提供服务。只要某个必要依赖还没准备好,过早调用就可能暴露半成品状态。

如果 A 的构造需要 B,B 的构造又需要 A,就会形成无法按这条路径完成的循环。某些属性注入循环在特定条件下可能被处理,但这不是所有循环都能解决的保证,还受配置和代理等因素影响。

先看定义,再看实例与代理

初始化阶段检查“能不能用”

对象还可能接收 Bean 名称、容器等 Aware 回调,随后进入初始化相关处理。常规路径里,初始化前的后置处理器会参与工作;一些注解回调本身就是由这类处理器发现和调用,并不是容器之外独立存在的一层魔法。

常见初始化机制包括标注的回调、InitializingBean 的方法及显式配置的初始化方法。若同时配置不同机制,需要遵循框架规定的顺序与去重语义,不能仅按源代码书写先后猜测执行顺序。

初始化适合校验必要配置、准备内部数据结构。把相互等待、依赖外部复杂启动顺序的重操作都放在这里,可能制造启动阶段阻塞甚至死锁。业务确实需要容器完整建立后的动作,可以再考虑与该生命周期阶段对应的机制。

交付的未必是原始对象

初始化之后,后置处理器仍可能包装 Bean,例如提供代理能力。调用方从容器拿到的引用可能是包装后的对象,底层业务目标则是另一个实例。这个区别能解释为什么直接 new 出同一类型,行为不一定等同于从容器获取。

不过,不是每个 Bean 都必然被代理,也不能说所有代理都只在唯一一个时刻产生。面试回答先给常规主线,再说明特殊路径要看实际处理器与作用域,比背“最后一步固定生成 AOP 代理”更准确。

同样,代理生效范围取决于具体调用路径。知道对象已被代理,不等于所有内部调用都会经过代理入口。这属于后续的代理语义,不能靠生命周期图自动推导。

销毁也要看作用域和关闭方式

容器管理的对象在适当的关闭流程中,可以执行相应销毁回调,释放自己负责的资源。但不同作用域的销毁责任并不完全一样;例如原型对象创建并交付之后,容器不会像管理常规单例那样自动跟踪其完整销毁过程。

进程被强制终止,也不能期待普通关闭回调保证运行。资源释放设计应与真实退出路径配合,不把一个注解当成对所有故障场景的保证。

回答时可以用“定义、创建与注入、初始化处理、可能的包装、按作用域销毁”建立骨架,再解释每个阶段的责任。若你想改变一个属性的默认配置,应该处理 Bean 定义,还是等对象已经创建后再修改实例?