这是《Spring Boot 与 Spring Cloud 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 本章整理 Spring Boot 与 Spring Cloud 高频面试题。回答时先给结论,再讲原理、场景和边界,最好结合一次真实故障或调优数据。
34.1 IoC 与 DI
问题:IoC 和 DI 是什么关系?
回答:
IoC 是设计思想:对象创建和装配的控制权交给容器。
DI 是实现方式:容器通过构造器、setter、字段等方式注入依赖。
加分点:
- BeanDefinition 是图纸;
- BeanFactory 是基础容器;
- ApplicationContext 增加事件、资源、环境能力;
- 构造器注入便于测试并暴露职责膨胀。
34.2 Bean 生命周期
问题:Bean 生命周期包含哪些阶段?
回答:
BeanDefinition 解析
-> 实例化
-> 属性填充
-> Aware 回调
-> before initialization
-> @PostConstruct / afterPropertiesSet / initMethod
-> after initialization
-> Bean 就绪
-> 销毁回调
加分点:
- BeanPostProcessor 是 AOP 的基础;
- 代理通常在初始化后生成;
@PreDestroy、DisposableBean、destroyMethod 的顺序;- singleton 和 prototype 生命周期差异。
34.3 循环依赖
问题:Spring 如何处理循环依赖?
回答:
- 三级缓存针对单例 setter/字段注入;
- 提前暴露早期引用;
- 构造器循环依赖不能解决;
- prototype 不能解决;
- Spring Boot 2.6+ 默认禁止。
正确表达:
循环依赖能被机制处理,不代表设计合理。更应拆分职责、引入事件或第三方编排服务。
34.4 AOP 失效
问题:为什么 @Transactional 自调用失效?
回答:
调用方拿到的是代理对象。
代理对象进入目标对象执行 create()。
create() 内部 this.validate() 是目标对象调用,不再经过代理。
解决:
- 拆分 Bean;
- 外部调用;
AopContext.currentProxy();- 重新设计职责。
其他失效:
| 场景 | 原因 |
|---|---|
| private 方法 | 代理无法拦截 |
| final 方法 | CGLIB 无法覆盖 |
| 异常被吞 | 拦截器看不到 |
| 受检异常 | 默认不回滚 |
| 多线程 | 事务绑定线程 |
34.5 Spring Boot 自动装配
问题:自动装配如何工作?
回答:
@EnableAutoConfiguration导入选择器;- 读取自动配置类清单;
- Spring Boot 3 使用
AutoConfiguration.imports; - 通过条件注解过滤;
- 按
@AutoConfigureBefore/After排序; - 注册 BeanDefinition。
常见条件:
@ConditionalOnClass
@ConditionalOnMissingBean
@ConditionalOnProperty
@ConditionalOnWebApplication
排查方式:--debug 输出 conditions report。
34.6 配置优先级
问题:Spring Boot 配置谁优先?
回答方向:
命令行通常优先
环境变量次之
配置中心视客户端实现
本地 profile 文件
默认配置
不要背死顺序。强调真实结果由 PropertySource 顺序决定,可用 /actuator/env 或 Environment 输出确认。
34.7 事务传播
问题:REQUIRES_NEW 和 NESTED 有什么区别?
| 行为 | 说明 |
|---|---|
| REQUIRES_NEW | 挂起当前事务,开启独立新事务 |
| NESTED | 在当前事务内使用保存点,局部回滚 |
风险:
- REQUIRES_NEW 占用第二个连接;
- 高并发可能耗尽连接池;
- NESTED 依赖数据库保存点支持;
- 外层回滚会影响嵌套事务。
34.8 Starter 设计
问题:如何设计一个 Starter?
回答:
- 拆分 core、api、autoconfigure、starter;
- 不扫描用户包;
- 使用
@ConfigurationProperties; - 条件装配提供默认 Bean;
- 允许用户覆盖;
- 提供超时和资源释放;
- 暴露指标和健康检查;
- 注册文件使用正确版本格式;
- 提供配置提示;
- 编写自动配置测试。
34.9 微服务拆分
问题:如何拆微服务?
回答框架:
1. 梳理业务流程和通用语言
2. 识别限界上下文
3. 确定数据所有权
4. 评估变更频率和团队边界
5. 从粗粒度服务开始
6. 建立契约和观测
7. 逐步独立数据库
反模式:
- 按技术层拆;
- 共享数据库;
- 小团队拆过多服务;
- 两个服务总是一起发布;
- 没有明确 owner。
34.10 服务发现
问题:注册中心怎么选?
回答维度:
- AP/CP 取舍;
- 健康检查模型;
- 多环境隔离;
- 权限;
- 运维成本;
- 与 K8s 或云平台集成;
- 团队已有生态。
关键结论:
服务发现通常偏 AP,消费者本地缓存服务列表;
即使有注册中心,也必须保留超时、重试和熔断。
34.11 网关职责
问题:网关应该做什么,不该做什么?
应该:
- 路由;
- 认证;
- 限流;
- 观测;
- 灰度;
- 安全 Header;
- TLS 终止。
不应该:
- 复杂业务规则;
- 数据库访问;
- 聚合复杂业务数据;
- 频繁发布的核心逻辑。
34.12 熔断限流
问题:熔断器三个状态是什么?
CLOSED -> OPEN -> HALF_OPEN -> CLOSED/OPEN
参数:
- 统计窗口;
- 最小请求数;
- 失败率;
- 慢调用比例;
- 打开时长;
- 半开探测数。
强调:
- 先有超时;
- 重试要幂等且有预算;
- fallback 必须记录指标;
- 降级要符合业务语义。
34.13 分布式事务
问题:下单跨服务如何保证一致性?
回答:
- 先定义状态机;
- 本地操作用本地事务;
- 事件可靠投递用 Outbox;
- 每一步幂等;
- 失败按 Saga/TCC 补偿;
- 定期对账;
- 异常进入人工处理。
不推荐只回答“用 Seata”。框架只是实现手段,业务状态和补偿设计才是核心。
34.14 优雅停机
问题:K8s 下 Spring Boot 如何无损发布?
回答:
readiness 探针摘流
-> 收到 SIGTERM
-> server.shutdown=graceful
-> 停止接收新请求
-> 等待在途请求
-> 关闭线程池和消费者
-> 退出
配套:
- preStop sleep 等摘流传播;
- terminationGracePeriodSeconds 大于应用等待;
- 消息处理完再提交 offset;
- 发布压测验证。
34.15 性能排查
问题:接口 P99 突然变高怎么排查?
回答:
1. 确认影响范围和时间
2. 看 QPS 和错误率
3. trace 定位慢在哪个 span
4. 检查 DB / Redis / 下游
5. 检查线程池和连接池
6. 检查 GC 和 CPU throttling
7. 关联发布和配置变更
8. 修复后验证
加分点:区分平均值和 P99,指出慢请求采样要保留。
34.16 容器内存
问题:Pod memory limit 2Gi,-Xmx 应该设多大?
回答:
不能等于 limit,因为还有:
- Metaspace;
- 线程栈;
- Direct memory;
- CodeCache;
- Native;
- OS 余量。
可用:
-XX:MaxRAMPercentage=60.0
再根据监控和 OOMKilled 情况调整。
本章小结
Spring 面试要重点讲清 IoC、Bean 生命周期、AOP、事务、自动装配、配置、Starter、微服务拆分、服务治理、分布式一致性和生产排障。高质量回答应包含场景、边界、证据和验证,而不是罗列注解。
思考题
- 哪些问题回答时必须说明 Spring Boot 版本?
- 如何用自动装配报告排查 Bean 未创建?
- 如何把一次事务失效讲成完整案例?
- 服务拆分如何证明不是过度设计?
- 微服务面试如何展示生产经验?