Spring 核心原理
候选人小刘在字节面试时被问到:"Spring 的 IoC 容器是怎么工作的?"
他背出了"控制反转、依赖注入"八个字。面试官追问:"那 Bean 的生命周期呢?"
小刘愣了一下,勉强说了个大概。面试官又问:"Aware 接口是干嘛用的?什么场景会用到?"
小刘开始擦汗。
【面试官心理】 我问他 IoC,不是想听背书。我想知道的是:他有没有亲手 debug 过容器,有没有理解过 Spring 是怎么从"配置"变成"Bean"的。BeanPostProcessor、BeanFactoryPostProcessor、FactoryBean——这几个概念能答清楚的基本都有源码阅读经验。
一、Spring IoC 容器 🔴
1.1 问题拆解
第一层:怎么用? 面试官问:"Spring 是怎么创建对象的?" 候选人答:"通过依赖注入..."(太表面)
第二层:底层实现 面试官追问:"那 BeanFactory 和 ApplicationContext 有什么区别?" 候选人答:...(开始卡)
第三层:边界缺陷 面试官追问:"BeanFactoryPostProcessor 和 BeanPostProcessor 的区别是什么?分别在什么时候执行?" 候选人答:...(P5/P6 分水岭)
第四层:选型 trade-off 面试官追问:"Spring 为什么用三级缓存解决循环依赖,而不是直接告诉你"有循环依赖"?" 候选人答:...(P7 拉开差距)
1.2 错误示范
候选人原话:"Spring IoC 就是控制反转,交给 Spring 管理 Bean 的创建和依赖关系..."
问题诊断:
- 只背了概念,完全没看过源码
- 不知道 Bean 是怎么从配置变成对象的
- 不理解 Spring 的扩展点设计
面试官内心 OS:"又是一个背八股的..."
1.3 标准回答
原理+实战闭环:
这个实现有什么问题?
- 没有依赖注入:Bean 之间的依赖关系怎么处理?
- 没有生命周期管理:创建前/后要不要做点什么?
- 没有扩展机制:怎么让用户自定义初始化逻辑?
Spring 的解决方案:
Spring 的 IoC 容器工作流程:
- 加载配置:XML/注解/JavaConfig
- 解析 BeanDefinition:把配置转成 BeanDefinition 对象
- 注册 BeanDefinition:放入 BeanDefinitionRegistry
- 实例化 Bean:根据 BeanDefinition 创建实例
- 依赖注入:填充属性,处理 @Autowired、@Value
- 生命周期回调:InitializingBean、@PostConstruct、BeanPostProcessor
- 缓存 Bean:放入 singletonObjects 一级缓存
【面试官心理】 我追问他 BeanPostProcessor,是想看他有没有真正理解 Spring 的扩展机制。知道这个接口的占 30%,能说清楚执行时机的占 10%。能答到这一层的,基本都有阅读源码的习惯。
1.4 追问升级
P6/P7 差距拉开点:
为什么需要三级缓存?
- 一级缓存:成品 Bean,可以直接使用
- 二级缓存:半成品 Bean,已经实例化但未完成属性注入和初始化
- 三级缓存:Bean 工厂,用于创建代理对象
这样设计是为了解决循环依赖时创建代理对象的问题。如果只有一级缓存,无法区分成品和半成品;如果只有两级缓存,无法处理代理对象的创建时机。
二、Spring AOP 🔴
2.1 问题拆解
第一层:怎么用? 面试官问:"AOP 怎么用?" 候选人答:"用 @Aspect 注解定义切面..."(基本 API)
第二层:底层实现 面试官追问:"JDK 动态代理和 CGLIB 动态代理有什么区别?Spring 怎么选?" 候选人答:...(开始模糊)
第三层:边界缺陷 面试官追问:"Spring Boot 为什么默认用 CGLIB?JDK 动态代理有什么限制?" 候选人答:...(P6/P7 分水岭)
第四层:源码细节 面试官追问:"AOP 代理是怎么创建和执行的?invoke 方法里做了什么?" 候选人答:...(P7 拉开差距)
2.2 错误示范
候选人原话:"AOP 是面向切面编程,可以用来做日志、事务、权限控制..."
问题诊断:
- 只背了概念,不知道底层是怎么实现的
- 不理解 JDK 代理和 CGLIB 的区别
- 不知道 Spring 是怎么决定用哪种代理的
面试官内心 OS:"这个候选人肯定没看过 AOP 源码,只背了概念..."
2.3 标准回答
最简实现对比:
这个写法会在哪翻车?
- JDK 代理要求目标类必须有接口,没有接口就用不了
- CGLIB 代理基于继承,无法代理 final 类和 final 方法
- Spring 需要一个策略来决定用哪种代理
Spring 的代理选择策略:
为什么 Spring Boot 默认用 CGLIB?
- 不需要接口:业务类通常没有接口,直接代理实现类更方便
- 强制 cglib:@EnableAspectJAutoProxy(proxyTargetClass = true) 是默认值
- 性能考虑:CGLIB 在初始化时比 JDK 代理慢,但运行时调用性能相当
【面试官心理】 我追问他 JDK 和 CGLIB 的区别,是想看他有没有实际用过、对比过。知道区别的占 60%,能说出 Spring 为什么默认用 CGLIB 的占 20%。能答到这一层的,基本都有排障经验。
2.4 生产避坑
同一个类内部调用会导致 AOP 失效!这是生产事故的高发区。例如下面的代码:
正确做法:注入自己或者用 ApplicationContext 获取代理对象。
三、Spring 事务 🔴
3.1 问题拆解
第一层:怎么用? 面试官问:"@Transactional 怎么用?" 候选人答:"加在方法或类上,开启事务..."(基本使用)
第二层:底层实现 面试官追问:"事务的传播行为有几种?REQUIRED 和 REQUIRES_NEW 有什么区别?" 候选人答:...(传播行为是高频追问点)
第三层:边界缺陷 面试官追问:"什么情况下 @Transactional 会失效?" 候选人答:...(生产事故高发区)
第四层:源码细节 面试官追问:"TransactionSynchronizationManager 是怎么管理事务的?" 候选人答:...(P7 拉开差距)
3.2 错误示范
候选人原话:"@Transactional 可以开启事务,出异常就会回滚..."
问题诊断:
- 不知道默认只对 RuntimeException 回滚
- 不知道 private 方法事务会失效
- 不理解事务传播行为的真正含义
面试官内心 OS:"这个候选人肯定没踩过事务的坑..."
3.3 标准回答
事务失效的 7 种情况:
- 同一个类内部调用:不走代理对象,事务不生效
- private 方法:JDK 动态代理无法代理 private 方法
- 非 RuntimeException:默认只对 RuntimeException 回滚
- rollbackFor 配置错误:没有配置对应的异常类型
- 新开启事务:传播行为配置错误,导致没有开启新事务
- 多数据源:没有配置正确的事务管理器
- try-catch 捕获异常:异常被吞掉,事务无法感知
传播行为的真正含义:
【面试官心理】 我追问他事务失效的场景,是想看他有没有实际踩过坑。能说出 3 种以上的,基本都有生产排障经验。能说清楚"同一个类内部调用事务失效"这个坑的,基本都看过 Spring 源码。
3.4 生产避坑
事务和锁的顺序问题:
事务和锁的顺序是生产事故的高发区。建议:先开启事务,再获取锁,最后操作数据。这样锁的粒度和事务的边界是匹配的。
四、Spring MVC 请求处理流程 🟡
4.1 问题拆解
第一层:怎么用? 面试官问:"Spring MVC 的请求处理流程是什么?" 候选人答:"DispatcherServlet 接收请求..."(基本流程)
第二层:组件协作 面试官追问:"HandlerMapping 和 HandlerAdapter 有什么区别?" 候选人答:...(组件职责)
第三层:扩展点 面试官追问:"怎么自定义参数解析器?什么场景会用到?" 候选人答:...(P6 拉开差距)
4.2 标准回答
关键组件:
- DispatcherServlet:前端控制器,统一处理请求入口
- HandlerMapping:根据 URL 找到对应的 Handler(Controller 方法)
- HandlerAdapter:执行 Handler,返回 ModelAndView
- ViewResolver:解析视图名,找到具体的 View 实现
- View:渲染模型数据,生成 HTML/JSON 等
【面试官心理】 我追问他自定义参数解析器,是想看他有没有扩展过 Spring MVC。能回答出来的,基本都有过"要给前端提供非标准参数格式"的实战经验。
五、Spring 扩展点 🟡
5.1 核心扩展点
5.2 生产避坑
BeanFactoryPostProcessor 和 BeanPostProcessor 名字很像,但作用完全不同:
- BeanFactoryPostProcessor:修改 BeanDefinition,可以在 Bean 实例化前改变 Bean 的配置
- BeanPostProcessor:处理 Bean 实例,在 Bean 初始化前后做增强
记不住?那就记住:BeanDefinition 是图纸,Bean 是成品。Factory 在图纸阶段动手,Processor 在成品阶段动手。
六、学习路径指引
准备 Spring 面试时,建议先从"事务失效"这个高频追问点入手,因为这既是面试重点,也是生产事故的高发区。
七、工程选型
什么场景用 Spring
适用场景:
- 企业级应用:Spring Boot 快速搭建微服务
- 复杂依赖管理:IoC 容器统一管理 Bean 依赖
- 切面编程:统一处理日志、事务、安全
- 响应式编程:Spring WebFlux
不适用场景:
- 极致性能:Spring 封装较多,性能敏感场景考虑裸 JDBC
- 极简项目:Spring Boot 虽然简单,但对 GraalVM 原生编译不友好
- 函数式场景:考虑 Vert.x 或 Project Reactor
八、生产避坑总结
生产环境遇到 Spring 问题,先看日志:BeanCreationException 说明 Bean 实例化失败,BeanNotOfRequiredTypeException 说明类型不匹配,NoSuchBeanDefinitionException 说明依赖缺失。