📌 PDF :大白话说Java面试题 --- 06_Spring篇
第11题:说一下 Spring Bean 的生命周期
📚 回答:
- 核心考点 : Spring Bean 的生命周期是 Spring 框架最核心的机制之一,大厂面试不会只问"有哪几个阶段",而是深入考察 BeanDefinition 的合并与后置处理 (
MergedBeanDefinitionPostProcessor)、三级缓存解决循环依赖的源码级原理 (singletonObjects/earlySingletonObjects/singletonFactories)、推断构造方法的完整算法 (autowireConstructor)、初始化阶段的三层回调顺序 (@PostConstruct→InitializingBean→ 自定义 init)、AOP 代理的创建时机与BeanPostProcessor的作用 、以及SmartInitializingSingleton和DestructionAwareBeanPostProcessor等扩展点。面试官真正想判断的是:你是否能从源码层面理解 Spring 容器的完整创建链路,以及能否在循环依赖、AOP 代理、初始化顺序等生产级场景中定位和解决问题。
1. 生命周期的完整阶段------从源码视角梳理
Spring Bean 的生命周期是一个精密的流水线,涉及 IoC 容器、依赖注入、AOP 代理、事件机制等多个子系统的协同。以下是完整的生命周期阶段:
【阶段1】加载 BeanDefinition
↓
【阶段2】BeanDefinition 合并与后置处理
↓
【阶段3】推断构造方法并实例化
↓
【阶段4】依赖注入(属性填充)
↓
【阶段5】初始化前处理(Aware 接口、@PostConstruct)
↓
【阶段6】初始化(InitializingBean、自定义 init)
↓
【阶段7】初始化后处理(AOP 代理创建)
↓
【阶段8】放入单例池,可供使用
↓
【阶段9】容器关闭时销毁(@PreDestroy、DisposableBean、自定义 destroy)
-
1.1 阶段1:加载 BeanDefinition
Spring 启动时,通过
BeanDefinitionReader读取配置(XML、注解、Java Config),将类信息解析为BeanDefinition对象,注册到BeanDefinitionRegistry(即DefaultListableBeanFactory)。配置方式 对应的 Reader 示例 XML XmlBeanDefinitionReader<bean id="userService" class="..."/>注解扫描 ClassPathBeanDefinitionScanner@ComponentScan("com.example")Java Config AnnotatedBeanDefinitionReader@Bean public UserService userService() {...} -
1.2 阶段2:BeanDefinition 合并与后置处理
在实例化之前,Spring 会执行
MergedBeanDefinitionPostProcessor的postProcessMergedBeanDefinition()方法,用于处理@Autowired、@Value、@PostConstruct等注解的元数据缓存:后置处理器 作用 AutowiredAnnotationBeanPostProcessor缓存 @Autowired、@Value注解的注入点CommonAnnotationBeanPostProcessor缓存 @Resource、@PostConstruct、@PreDestroy注解RequiredAnnotationBeanPostProcessor处理 @Required注解(已废弃)为什么要缓存? 避免每次创建 Bean 时都通过反射扫描注解,提升性能。
-
1.3 阶段3:推断构造方法并实例化
Spring 通过
AbstractAutowireCapableBeanFactory.createBeanInstance()方法推断构造方法并创建实例。构造方法推断规则:
场景 推断结果 只有一个无参构造 使用无参构造(默认) 只有一个有参构造 使用唯一的有参构造 多个构造方法,其中一个标注 @Autowired(required=true)使用标注的构造方法 多个构造方法,多个标注 @Autowired(required=false)使用参数最多的构造方法 多个构造方法,无 @Autowired使用无参构造;若无无参构造,则报错 java@Service public class UserService { private final UserRepository userRepository; private final OrderRepository orderRepository; // Spring 会选择这个构造方法(参数最多且 @Autowired) @Autowired public UserService(UserRepository userRepository, OrderRepository orderRepository) { this.userRepository = userRepository; this.orderRepository = orderRepository; } public UserService(UserRepository userRepository) { this.userRepository = userRepository; } }构造方法参数解析 :按
byType先查找,若同类型有多个 Bean,再按byName匹配。 -
1.4 阶段4:依赖注入(属性填充)
实例化后,Spring 执行
populateBean()方法填充属性:InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation():允许在属性注入前修改 Bean 实例;InstantiationAwareBeanPostProcessor.postProcessProperties():解析并注入@Autowired、@Value、@Resource等注解标记的属性;applyPropertyValues():处理 XML 配置中的<property>标签。
注入方式对比:
注入方式 注解 注入时机 特点 构造器注入 无(或 @Autowired)实例化时 依赖必填,不可变,推荐 Setter 注入 @Autowired属性填充时 依赖可选,可重新设置 字段注入 @Autowired属性填充时 代码简洁,但测试困难,不推荐 -
1.5 阶段5:初始化前处理(Aware 接口、@PostConstruct)
属性填充完成后,进入
initializeBean()方法,首先执行Aware接口:Aware 接口 注入内容 用途 BeanNameAwareBean 的名称 获取自身 BeanName BeanFactoryAwareBeanFactory 容器 获取容器引用 ApplicationContextAwareApplicationContext 容器 获取应用上下文 EnvironmentAwareEnvironment 获取配置环境 ResourceLoaderAwareResourceLoader 加载资源文件 Aware 接口的执行顺序:按上述表格从上到下依次执行。
然后执行
BeanPostProcessor.postProcessBeforeInitialization():后置处理器 作用 ApplicationContextAwareProcessor处理 ApplicationContextAware等接口InitDestroyAnnotationBeanPostProcessor调用 @PostConstruct标注的方法@PostConstruct的执行 :由InitDestroyAnnotationBeanPostProcessor处理,通过反射调用标注了@PostConstruct的方法。 -
1.6 阶段6:初始化(InitializingBean、自定义 init)
BeanPostProcessor.postProcessBeforeInitialization()执行完毕后,进入真正的初始化阶段:【初始化顺序】 ↓ 1. InitializingBean.afterPropertiesSet() ↓ 2. 自定义 init-method(@Bean(initMethod="...") 或 XML init-method)三层初始化回调的执行顺序:
顺序 回调方式 来源 说明 1 @PostConstructJSR-250 标准 最先执行,推荐 2 InitializingBean.afterPropertiesSet()Spring 接口 次之,侵入性较强 3 自定义 init-method配置指定 最后执行,最灵活 java@Service public class UserService implements InitializingBean { @PostConstruct public void postConstruct() { System.out.println("1. @PostConstruct 执行"); } @Override public void afterPropertiesSet() throws Exception { System.out.println("2. InitializingBean 执行"); } public void customInit() { System.out.println("3. 自定义 init-method 执行"); } } // Java Config 中指定自定义 init @Bean(initMethod = "customInit") public UserService userService() { return new UserService(); } -
1.7 阶段7:初始化后处理(AOP 代理创建)
初始化完成后,执行
BeanPostProcessor.postProcessAfterInitialization()。这是 AOP 代理创建的关键时机:后置处理器 作用 AbstractAutoProxyCreator检查 Bean 是否匹配切点,若匹配则创建代理对象 为什么 AOP 代理在初始化后创建?
因为代理对象需要包装完整的 Bean 实例(包括已注入的依赖和已执行的初始化逻辑)。如果在实例化时创建代理,后续的依赖注入和初始化会作用于代理对象,可能导致问题。
代理创建后的 Bean 类型:
- JDK 代理:
com.sun.proxy.$ProxyXX(实现目标接口) - CGLIB 代理:
UserService$$EnhancerBySpringCGLIB(继承目标类)
- JDK 代理:
-
1.8 阶段8:放入单例池
初始化完成后,Bean(或代理对象)被放入
DefaultSingletonBeanRegistry的三级缓存:缓存级别 字段名 说明 一级缓存 singletonObjects存放完全初始化好的 Bean(成品) 二级缓存 earlySingletonObjects存放提前暴露的 Bean(半成品,用于解决循环依赖) 三级缓存 singletonFactories存放 Bean 的 ObjectFactory(用于创建代理对象) -
1.9 阶段9:销毁
容器关闭时,执行销毁回调:
顺序 回调方式 来源 1 @PreDestroyJSR-250 标准 2 DisposableBean.destroy()Spring 接口 3 自定义 destroy-method配置指定 java@Service public class UserService implements DisposableBean { @PreDestroy public void preDestroy() { System.out.println("1. @PreDestroy 执行"); } @Override public void destroy() throws Exception { System.out.println("2. DisposableBean 执行"); } public void customDestroy() { System.out.println("3. 自定义 destroy-method 执行"); } }
2. 三级缓存解决循环依赖的源码级原理
循环依赖是 Spring Bean 生命周期中最经典的问题,Spring 通过三级缓存解决单例 Bean 的循环依赖。
-
2.1 什么是循环依赖?
java@Service public class UserService { @Autowired private OrderService orderService; // 依赖 OrderService } @Service public class OrderService { @Autowired private UserService userService; // 依赖 UserService }创建
UserService需要OrderService,创建OrderService又需要UserService,形成循环。 -
2.2 三级缓存的结构与作用
java// DefaultSingletonBeanRegistry 源码 public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { // 一级缓存:成品单例池 private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 二级缓存:提前暴露的对象(半成品) private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); // 三级缓存:单例工厂(用于创建代理对象) private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); }缓存 存放内容 作用 singletonObjects完全初始化好的 Bean 最终使用的单例池 earlySingletonObjects已实例化但未初始化的 Bean 解决循环依赖,暴露半成品 singletonFactoriesObjectFactory(可生成 Bean 或代理)延迟创建代理对象,解决循环依赖中的 AOP 代理问题 -
2.3 循环依赖的解决流程
以
UserService→OrderService→UserService为例:1. getBean("userService") ↓ 2. 从 singletonObjects 查找 → 未找到 ↓ 3. 实例化 UserService(调用构造方法) ↓ 4. 将 UserService 的 ObjectFactory 放入 singletonFactories(三级缓存) ↓ 5. 开始属性填充(populateBean),发现需要 OrderService ↓ 6. getBean("orderService") ↓ 7. 实例化 OrderService ↓ 8. 将 OrderService 的 ObjectFactory 放入 singletonFactories ↓ 9. 开始属性填充,发现需要 UserService ↓ 10. getBean("userService") ------ 再次获取 UserService ↓ 11. 从 singletonObjects 查找 → 未找到 ↓ 12. 从 earlySingletonObjects 查找 → 未找到 ↓ 13. 从 singletonFactories 查找 → 找到!调用 ObjectFactory.getObject() ↓ 14. 返回 UserService 的半成品(已实例化,未初始化) ↓ 15. OrderService 完成属性填充、初始化 ↓ 16. OrderService 放入 singletonObjects ↓ 17. 回到 UserService 的属性填充,OrderService 已可用 ↓ 18. UserService 完成初始化 ↓ 19. UserService 放入 singletonObjects -
2.4 为什么需要三级缓存?二级缓存不够吗?
核心原因:AOP 代理对象的创建时机问题。
如果只有二级缓存,循环依赖中的 Bean 在提前暴露时必须是最终形态(包括代理对象)。但 AOP 代理在初始化后(
postProcessAfterInitialization)才创建,而循环依赖的属性填充发生在初始化之前。三级缓存的
ObjectFactory作用 :延迟创建代理对象。当从三级缓存获取 Bean 时,ObjectFactory会判断是否需要提前创建代理(通过SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference()),确保暴露的是代理对象而非原始对象。java// 三级缓存的 ObjectFactory 源码简化 addSingletonFactory(beanName, () -> { // 如果有 AOP 代理,提前创建代理对象;否则返回原始对象 return getEarlyBeanReference(beanName, mbd, bean); }); -
2.5 循环依赖的局限
场景 是否支持 原因 单例 + 属性注入 ✅ 支持 三级缓存解决 单例 + 构造器注入 ❌ 不支持 构造器注入时 Bean 尚未实例化,无法提前暴露 原型(Prototype) ❌ 不支持 原型 Bean 不缓存,每次创建都是新实例 构造器循环依赖的解决方案:
- 改用 Setter/字段注入;
- 使用
@Lazy延迟注入; - 重构代码,消除循环依赖。
3. BeanPostProcessor 的作用与执行时机
BeanPostProcessor 是 Spring 提供的扩展接口,允许在 Bean 初始化的前后插入自定义逻辑。
| 接口 | 方法 | 执行时机 | 典型用途 |
|---|---|---|---|
BeanPostProcessor |
postProcessBeforeInitialization |
初始化之前(Aware 之后) | 自定义初始化前处理 |
BeanPostProcessor |
postProcessAfterInitialization |
初始化之后 | AOP 代理创建、自定义初始化后处理 |
InstantiationAwareBeanPostProcessor |
postProcessBeforeInstantiation |
实例化之前 | 自定义实例化逻辑(如代理替换) |
InstantiationAwareBeanPostProcessor |
postProcessAfterInstantiation |
实例化之后 | 控制是否继续属性填充 |
InstantiationAwareBeanPostProcessor |
postProcessProperties |
属性填充时 | 自定义属性注入(如 @Autowired 处理) |
MergedBeanDefinitionPostProcessor |
postProcessMergedBeanDefinition |
BeanDefinition 合并后 | 缓存注解元数据 |
重要后置处理器汇总:
| 后置处理器 | 实现的接口 | 核心作用 |
|---|---|---|
AutowiredAnnotationBeanPostProcessor |
InstantiationAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor |
处理 @Autowired、@Value |
CommonAnnotationBeanPostProcessor |
InstantiationAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor |
处理 @Resource、@PostConstruct、@PreDestroy |
ApplicationContextAwareProcessor |
BeanPostProcessor |
处理 Aware 接口 |
AbstractAutoProxyCreator |
InstantiationAwareBeanPostProcessor, BeanPostProcessor |
创建 AOP 代理 |
RequiredAnnotationBeanPostProcessor |
MergedBeanDefinitionPostProcessor |
处理 @Required |
4. 生产环境避坑指南
-
4.1
@PostConstruct中不能使用@Value注入的配置?可以!
@Value的注入在属性填充阶段已完成,@PostConstruct在属性填充之后执行,因此可以正常使用。java@Service public class UserService { @Value("${server.port}") private String port; @PostConstruct public void init() { System.out.println("端口: " + port); // ✅ 可以正常获取 } } -
4.2 初始化方法中抛出异常会怎样?
如果
@PostConstruct、afterPropertiesSet()或自定义 init 方法抛出异常,Spring 会包装为BeanCreationException,该 Bean 的创建失败,不会放入单例池。如果其他 Bean 依赖它,也会级联失败。 -
4.3 不要在
BeanPostProcessor中触发其他 Bean 的初始化如果在
BeanPostProcessor中调用getBean()获取其他 Bean,可能导致该后置处理器尚未完全注册,引发不可预期的问题。 -
4.4 AOP 代理对象的类型判断
java@Autowired private UserService userService; public void test() { // ❌ 错误:JDK 代理时 instanceof 判断失败 if (userService instanceof UserServiceImpl) { ... } // ✅ 正确:使用 Spring 工具类 if (AopUtils.isAopProxy(userService)) { Class<?> targetClass = AopProxyUtils.ultimateTargetClass(userService); } // ✅ 正确:判断接口 if (userService instanceof UserService) { ... } } -
4.5 延迟初始化
@Lazy对于启动耗时较长的 Bean,可以使用
@Lazy延迟初始化:java@Service @Lazy public class HeavyService { // 只有在首次被注入或获取时才初始化 } -
4.6 销毁方法中不要依赖其他 Bean
容器关闭时,Bean 的销毁顺序不确定。如果在
destroy()中调用其他 Bean 的方法,可能该 Bean 已销毁,导致空指针。
5. 面试官追问与高分回答模板
-
追问 1:"说一下 Spring Bean 的生命周期"
低分回答:"包括实例化、属性注入、初始化、使用、销毁几个阶段。"(太笼统,没有触及源码)
高分回答:
"Spring Bean 的生命周期是一个完整的流水线,我从源码层面梳理:
- 加载 BeanDefinition :通过
BeanDefinitionReader解析配置,注册到BeanDefinitionRegistry; - BeanDefinition 合并与后置处理 :
MergedBeanDefinitionPostProcessor缓存@Autowired、@PostConstruct等注解元数据; - 推断构造方法并实例化 :根据
@Autowired标注、参数数量等规则选择构造方法,通过反射创建实例; - 依赖注入(populateBean) :
InstantiationAwareBeanPostProcessor处理@Autowired、@Value、@Resource注入; - 初始化前处理 :执行
Aware接口(BeanNameAware、ApplicationContextAware等),然后BeanPostProcessor.postProcessBeforeInitialization()调用@PostConstruct; - 初始化 :
InitializingBean.afterPropertiesSet()→ 自定义init-method; - 初始化后处理 :
BeanPostProcessor.postProcessAfterInitialization()创建 AOP 代理; - 放入单例池 :三级缓存(
singletonObjects/earlySingletonObjects/singletonFactories); - 销毁 :
@PreDestroy→DisposableBean.destroy()→ 自定义destroy-method。
其中最关键的是三级缓存解决循环依赖 和AOP 代理在初始化后创建这两个设计。"
- 加载 BeanDefinition :通过
-
追问 2:"Spring 如何解决循环依赖?为什么需要三级缓存?"
低分回答:"通过三级缓存解决,提前暴露半成品 Bean。"(没有解释为什么需要三级)
高分回答:
"Spring 通过三级缓存解决单例 Bean 的属性注入循环依赖:
缓存 内容 作用 singletonObjects成品 Bean 最终使用的单例池 earlySingletonObjects半成品 Bean 提前暴露,供循环依赖注入 singletonFactoriesObjectFactory延迟创建代理对象 为什么需要三级缓存?
如果只有二级缓存,循环依赖中提前暴露的 Bean 必须是最终形态。但 AOP 代理在初始化后(
postProcessAfterInitialization)才创建,而循环依赖的属性填充发生在初始化之前。三级缓存的
ObjectFactory实现了延迟创建 :当从三级缓存获取 Bean 时,通过getEarlyBeanReference()判断是否需要提前创建代理对象,确保暴露的是代理对象而非原始对象。局限:只支持单例 + 属性注入的循环依赖。构造器注入和原型 Bean 的循环依赖不支持。"
-
追问 3:"@PostConstruct、InitializingBean、自定义 init-method 的执行顺序是什么?"
高分回答:
"执行顺序是:
@PostConstruct → InitializingBean.afterPropertiesSet() → 自定义 init-method回调 来源 特点 @PostConstructJSR-250 标准 最先执行,推荐,无侵入 InitializingBeanSpring 接口 次之,侵入性较强,需实现接口 init-method配置指定 最后执行,最灵活,可在 XML 或 @Bean中配置推荐优先使用
@PostConstruct,它是 Java 标准注解,不依赖 Spring 接口,代码更解耦。" -
追问 4:"构造器注入和字段注入有什么区别?Spring 推荐哪种?"
高分回答:
"| 维度 | 构造器注入 | 字段注入 |
|------|-----------|----------|
| 依赖保证 | 必填,Bean 创建时就必须提供 | 可选,可能为 null |
| 不可变性 | 可配合
final,实现不可变 | 不能加final|| 测试友好性 | 高,可直接 new 并传参 | 低,需要反射或 Spring 容器 |
| 循环依赖 | 不支持(可检测设计问题) | 支持(可能隐藏设计问题) |
| NPE 风险 | 低,依赖必有 | 高,可能忘记注入 |
Spring 官方推荐构造器注入(Spring 4+ 开始),原因:
- 依赖明确,不可变;
- 启动时就能发现循环依赖;
- 无需
@Autowired注解(Spring 4.3+ 单构造器自动注入)。
字段注入虽然代码简洁,但测试困难、可能隐藏循环依赖问题,不推荐。"
-
追问 5:"BeanPostProcessor 和 BeanFactoryPostProcessor 有什么区别?"
高分回答:
"| 维度 | BeanPostProcessor | BeanFactoryPostProcessor |
|------|-------------------|-------------------------|
| 作用对象 | Bean 实例 | BeanDefinition |
| 执行时机 | Bean 实例化之后、初始化前后 | BeanDefinition 加载完成后、Bean 实例化之前 |
| 典型用途 | AOP 代理创建、属性注入处理 | 修改 BeanDefinition(如
@ConfigurationProperties) || 代表实现 |
AbstractAutoProxyCreator、AutowiredAnnotationBeanPostProcessor|ConfigurationClassPostProcessor、PropertySourcesPlaceholderConfigurer|BeanFactoryPostProcessor在 Bean 实例化之前执行,可以修改 Bean 的定义(如修改作用域、属性值)。BeanPostProcessor在 Bean 实例化之后执行,可以修改 Bean 实例(如创建代理、注入依赖)。典型应用:
PropertySourcesPlaceholderConfigurer(处理${...}占位符)是BeanFactoryPostProcessor;@Autowired注入是BeanPostProcessor。" -
追问 6:"AOP 代理为什么在初始化后创建,而不是实例化时?"
高分回答:
"AOP 代理在初始化后(
postProcessAfterInitialization)创建,原因有三:- 依赖注入的完整性:代理对象需要包装完整的 Bean 实例,包括已注入的依赖。如果在实例化时创建代理,后续的属性填充会作用于代理对象,可能导致代理逻辑被覆盖或注入失败。
- 初始化回调的一致性 :
@PostConstruct、InitializingBean等初始化逻辑需要在代理对象上执行,确保代理能拦截这些回调(虽然通常不拦截)。 - 循环依赖的兼容性:如果 AOP 代理在实例化时创建,循环依赖中提前暴露的代理对象可能尚未完成依赖注入,导致其他 Bean 注入的是一个不完整的代理。
但这也带来一个问题:循环依赖中的 Bean 需要提前暴露代理对象。Spring 通过三级缓存的
ObjectFactory.getEarlyBeanReference()解决------在必要时提前创建代理,确保循环依赖注入的是代理对象而非原始对象。"
6. 方案选型速查表
| 业务场景 | 推荐方案 | 核心理由 |
|---|---|---|
| 初始化资源(连接池、线程池) | @PostConstruct |
标准注解,最先执行,无侵入 |
| 校验依赖完整性 | InitializingBean |
可抛出异常阻止 Bean 创建 |
| 复杂初始化逻辑 | 自定义 init-method |
最灵活,可配置化 |
| 释放资源(关闭连接池) | @PreDestroy |
标准注解,最先执行 |
| 获取容器上下文 | ApplicationContextAware |
Spring 提供的标准扩展点 |
| 动态修改 Bean 定义 | BeanFactoryPostProcessor |
实例化前修改 BeanDefinition |
| 创建 AOP 代理 | BeanPostProcessor |
初始化后包装代理对象 |
| 解决构造器循环依赖 | @Lazy 或重构 |
延迟注入或消除循环 |
💡 面试官想要的满分总结:
Spring Bean 的生命周期是一个从"定义"到"销毁"的精密流水线,核心阶段包括:加载 BeanDefinition → 合并与后置处理 → 推断构造方法实例化 → 依赖注入 → 初始化前处理(Aware +
@PostConstruct)→ 初始化(InitializingBean+ 自定义 init)→ 初始化后处理(AOP 代理)→ 放入单例池 → 销毁。理解生命周期必须抓住两个核心设计:三级缓存解决循环依赖 和BeanPostProcessor 扩展机制 。三级缓存中
singletonFactories的ObjectFactory延迟创建代理对象,是解决循环依赖中 AOP 代理问题的关键;BeanPostProcessor则在 Bean 创建的关键节点提供扩展能力,AOP 代理、依赖注入、注解处理都依赖于此。工程实践中,优先使用构造器注入 (Spring 官方推荐),优先使用
@PostConstruct做初始化 (标准注解、无侵入),警惕循环依赖 (构造器注入可提前暴露设计问题)。理解BeanPostProcessor和BeanFactoryPostProcessor的区别------前者操作 Bean 实例,后者操作 Bean 定义------是区分初级和高级开发者的分水岭。
觉得对您有帮助,麻烦 点点关注啦 ,您的关注是我创作的最大动力~ 🎯