type
status
date
slug
summary
tags
category
icon
password
catalog
sort
写给:
- 刚学 Spring 想一口气吃透 Bean 的新手
- 被循环依赖、作用域、生命周期虐到秃头的中级玩家
- 想在面试里把“Bean 怎么来的”讲到源码级的卷王
🧭 目录速览
1️⃣ Spring Bean 是啥?和普通 Java 对象有啥区别? {#1}
📚 官方文档坐标:Spring Framework Reference → Core → IoC Container → 1.3. Bean Overview “A bean is an object that is instantiated, assembled, and otherwise managed by a Spring IoC container.” Bean 就是一个被 Spring 全程托管的对象:帮你 new,帮你注入,帮你销毁,一条龙!
对比维度 | 普通 Java 对象 | Spring Bean |
创建方式 | new User() | 容器反射创建 |
生命周期 | 手动 new / GC | 容器全程托管 |
依赖注入 | 手动 setXxx() | @Autowired 自动注入 |
销毁 | 等待 GC | @PreDestroy 回调 |
一句话:Bean 就是被 Spring “领养”的 Java 对象,吃喝拉撒全管!
2️⃣ 容器是怎么“认”出它的? {#2}
📚 官方文档坐标:Core → Container → 1.10. Classpath Scanning and Managed Components
步骤 | 官方类 | 关键方法 | 源码链接 |
① 定位资源 | PathMatchingResourcePatternResolver#getResources | getResources("classpath*:com/example/**/*.class") | |
② 候选过滤 | ClassPathScanningCandidateComponentProvider#findCandidateComponents | 过滤 @Component | |
③ 解析注解 | AnnotatedBeanDefinitionReader#registerBean | 读 @Component 、@Lazy 等元数据 | |
④ 注册 | DefaultListableBeanFactory#registerBeanDefinition | beanDefinitionMap.put(beanName, definition) |
2.1 容器三层结构(面试常问)
- BeanFactory:只负责“拿 bean”
- ApplicationContext:加料版 BeanFactory,能发事件、读 i18n、扫注解
2.2 容器识别 Bean 的 3 步曲
步骤 | 关键类 | 说明 |
① 扫描 | ClassPathBeanDefinitionScanner | 扫 @Component 、@Service |
② 解析 | AnnotatedBeanDefinitionReader | 把注解转成 BeanDefinition |
③ 注册 | DefaultListableBeanFactory | 存到 Map<String, BeanDefinition> |
小抄:容器启动时,会把所有 *.class 变成 BeanDefinition,再按需实例化。
3️⃣ 从 .class 到“可用对象”的完整旅程 {#3}
3.1 单例 Bean 的三级缓存(解决循环依赖)
缓存级别 | 作用 | 是否线程安全 |
一级 singletonObjects | 已初始化完成的单例 | ✅ |
二级 earlySingletonObjects | 早期暴露(未初始化) | ✅ |
三级 singletonFactories | 创建早期引用的工厂 | ✅ |
面试题:Spring 如何解决 A→B→A 的循环依赖?答:三级缓存提前暴露 A 的早期引用,B 注入后继续完成 A 的初始化。
4️⃣ 生命周期:11 个阶段一次讲透 {#4}
📚 官方文档坐标:Core → Container → 1.6. Bean Lifecycle
4.1 生命周期全景
阶段 | 官方方法 | 源码行号 |
① 实例化 | AbstractAutowireCapableBeanFactory#createBeanInstance | |
② 属性注入 | populateBean | |
③ 初始化前 | CommonAnnotationBeanPostProcessor#postProcessBeforeInitialization | |
④ 初始化 | InitializingBean#afterPropertiesSet | |
⑤ 销毁 | DisposableBeanAdapter#destroy |
4.2 3 种初始化方式 & 执行顺序
方式 | 示例 | 顺序 |
注解 | @PostConstruct | 1️⃣ |
接口 | InitializingBean#afterPropertiesSet | 2️⃣ |
XML | init-method="customInit" | 3️⃣ |
4.3 销毁方法同理:先 @PreDestroy
→ DisposableBean
→ destroy-method
5️⃣ 作用域:单例、原型、request、session 的底层实现 {#5}
📚 官方文档坐标:Core → Container → 1.5. Bean Scopes
5.1 作用域速查表
作用域 | 实例数 | 生命周期 | 线程安全 | 典型场景 |
singleton | 1 | 容器 → 关闭 | ❌ 需手动保证 | Service、DAO |
prototype | ∞ | 每次 getBean | ❌ 无共享 | 有状态命令对象 |
request | 1/请求 | 请求 → 响应 | ✅ 线程隔离 | 用户请求上下文 |
session | 1/会话 | 登录 → 登出 | ✅ 会话隔离 | 购物车、登录态 |
5.2 Web 作用域的底层实现
- request 作用域:存到
HttpServletRequest#setAttribute
- session 作用域:存到
HttpSession#setAttribute
- 线程安全:靠
ThreadLocal<RequestAttributes>
隔离
5.3 自定义作用域实战:批处理任务作用域
6️⃣ 高级特性:条件、延迟、动态注册 {#6}
6.1 条件注解:@ConditionalOnClass
- 官方实现:
OnClassCondition
判断ClassUtils.isPresent
- 场景:只在类路径存在
RedisTemplate
时才注册RedisCacheManager
- 原理:在
BeanDefinition
注册阶段就排除不符合条件的 Bean
6.2 延迟加载:@Lazy
- 单例:默认预实例化,
@Lazy
改为首次使用时实例化
- 原型:无实际意义(本身就是每次创建)
6.3 动态注册:BeanDefinitionRegistryPostProcessor
7️⃣ 常见坑 & 解决方案 {#7}
坑点 | 现象 | 根因 | 解决 |
单例持有 request Bean | 内存泄漏 | 单例长期引用 request 作用域 Bean | 使用代理模式 @Scope(proxyMode = TARGET_CLASS) |
原型 Bean 循环依赖 | BeanCurrentlyInCreationException | 每次创建都触发新实例,无限递归 | 设计重构,引入中间层 |
session Bean 未序列化 | 集群部署报错 | 未实现 Serializable | 实现 Serializable ,或使用分布式会话 |
8️⃣ 最佳实践:怎么选作用域? {#8}
8.1 决策树(30 秒版)
8.2 性能与资源权衡
作用域 | 创建开销 | GC 压力 | 适用场景 |
singleton | 低 | 低 | 无状态服务 |
prototype | 高 | 高 | 有状态短暂对象 |
request | 中 | 中 | 请求上下文 |
9️⃣ 未来趋势:Spring 6.x & WebFlux {#9}
9.1 GraalVM 原生镜像
- AOT 编译:构建时生成 BeanDefinition,减少运行时反射
- 限制:Bean 构造函数需 public,避免动态
Class.forName
9.2 响应式 Bean 生命周期
- 非阻塞初始化:
@PostConstruct
可返回Mono<Void>
- Context 替代 request 作用域:通过
Reactor Context
传递请求信息
🔟 一张图总结 {#10}
写在最后:Spring Bean 就像乐高积木,选对作用域、用好生命周期,才能搭出既稳又快的系统。收藏本文,面试不慌,调优不慌! 🛠️
参考
- 作者:Honesty
- 链接:https://blog.hehouhui.cn/archives/spring-bean-lifecycle-deep-dive
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。