Spring 面试八股文 50 道|深度详解版(傻子都能看懂)
📖 学习指南
🎯 学习目标:通过本文,你将系统掌握 Spring 的核心知识,能够自信地应对任何相关面试问题。
适合人群
- 🔰 初学者:想系统学习 Spring 的开发者
- 🚀 有经验者:想深入理解 Spring 原理的高级开发者
- 💼 面试准备者:想刷 Spring 面试题的求职者
学习建议
- 先理解概念,再深入原理:先搞懂”是什么”,再搞懂”为什么”
- 结合实战场景:不要死记硬背,要理解实际应用场景
- 动手实践:在自己电脑上安装环境,执行本文的示例代码
- 反复复习:面试前一周,每天复习 10 个问题
学习时间估算
- ⏱️ 快速复习(只看一句话总结):2 小时
- 📚 系统学习(看深度解析):1-2 天
- 💪 深入理解(研究源码):1 周+
🗺️ 知识图谱
mindmap
root((Spring))
基础概念
核心概念1
核心概念2
核心概念3
高级特性
特性1
特性2
实战应用
应用场景1
应用场景2
性能优化
优化技巧1
优化技巧2
⚠️ 常见陷阱与误区
陷阱 1:概念理解错误
❌ 错误示例:
1 | |
✅ 正确做法:
- 正确理解概念
- 避免常见误区
陷阱 2:忽略边界条件
❌ 错误做法:
- 不考虑特殊情况
- 忽略异常处理
✅ 正确做法:
- 总是考虑边界条件
- 添加异常处理
💡 面试技巧
技巧 1:结构化回答
不要只回答”是什么”,要按照以下结构回答:
- 一句话总结(概念)
- 深度解析(原理、实现、优缺点)
- 面试加分回答(实际项目经验、源码理解、行业最佳实践)
技巧 2:结合实战场景
不要只背概念,要结合实际项目经验回答。
技巧 3:引导到你会的方向
如果遇到不会的问题,不要慌,可以引导到你会的方向。
🎯 实战演练(真实面试场景)
场景 1:请你设计一个系统?
回答思路:
- 需求分析:明确系统需求
- 技术选型:选择合适的技术栈
- 架构设计:设计系统架构
- 性能优化:考虑性能瓶颈和优化方案
🚀 学习路径总结
第一阶段:基础概念(1-2 天)
- 理解核心概念
- 掌握基本操作
- 完成入门教程
第二阶段:高级特性(2-3 天)
- 掌握高级特性
- 理解实现原理
- 完成进阶教程
第三阶段:实战应用(1 周+)
- 搭建实际项目
- 解决实战问题
- 阅读源码(可选)
第四阶段:面试准备(1 周)
- 刷完本文的所有问题
- 复习相关知识点
- 准备项目经验
- 模拟面试
📚 扩展学习资源
官方资源
书籍推荐
- 《Spring 实战》
- 《Spring 权威指南》
博客推荐
Spring 面试八股文 - 学习指南
🎯 学习目标:真正理解 Spring 的核心原理,而不仅仅是背答案
📖 适用人群:Java 初学者、准备面试的同学、想深入理解 Spring 的开发者
⏰ 预计学习时间:2-3 天(每天 2-3 小时)
🏆 学习成果:能够自信地回答任何 Spring 面试问题,并理解背后的原理
📚 学习路线(从易到难)
第一阶段:Spring 基础(Day 1)
- IOC(控制反转)和 DI(依赖注入) - 理解 Spring 的核心思想
- Bean 的作用域和生命周期 - 理解 Spring 如何管理对象
- @Component、@Service、@Repository、@Controller - 理解 Bean 的注册
- @Autowired 和 @Resource - 理解依赖注入的两种方式
第二阶段:Spring AOP(Day 2 - 上午)
- AOP 的基本概念 - 理解切面、切点、通知
- JDK 动态代理和 CGLIB 代理 - 理解 Spring AOP 的实现原理
- @Before、@After、@Around - 理解各种通知类型
- AOP 的实际应用 - 日志、事务、权限校验
第三阶段:Spring MVC(Day 2 - 下午)
- Spring MVC 的工作流程 - 理解请求如何到达 Controller
- @RequestParam、@PathVariable、@RequestBody - 理解参数绑定
- 拦截器和过滤器 - 理解请求拦截机制
- 统一异常处理 - 理解 @ControllerAdvice
第四阶段:Spring Boot(Day 3 - 上午)
- Spring Boot 的自动配置原理 - 理解 @EnableAutoConfiguration
- Spring Boot 的启动流程 - 理解 SpringApplication.run()
- 配置文件和属性注入 - 理解 @ConfigurationProperties 和 @Value
- Spring Boot Starter - 理解自定义 Starter
第五阶段:Spring Cloud(Day 3 - 下午)
- 服务注册与发现 - 理解 Eureka、Nacos
- 负载均衡 - 理解 Ribbon、LoadBalancer
- 声明式 HTTP 客户端 - 理解 Feign
- 服务熔断 - 理解 Hystrix、Resilience4j
🗺️ Spring 知识图谱
graph TD
A[Spring Framework] --> B[Spring Core]
A --> C[Spring AOP]
A --> D[Spring MVC]
A --> E[Spring Boot]
A --> F[Spring Cloud]
B --> B1[IOC 控制反转]
B --> B2[DI 依赖注入]
B --> B3[Bean 管理]
B --> B4[ApplicationContext]
C --> C1[动态代理]
C --> C2[切面 Aspect]
C --> C3[通知 Advice]
C --> C4[切点 Pointcut]
D --> D1[DispatcherServlet]
D --> D2[Controller]
D --> D3[Service]
D --> D4[Repository]
E --> E1[自动配置]
E --> E2[起步依赖]
E --> E3[Actuator]
E --> E4[CLI 工具]
F --> F1[注册中心]
F --> F2[配置中心]
F --> F3[网关]
F --> F4[熔断限流]
style A fill:#ff6b6b
style B fill:#4ecdc4
style C fill:#45b7d1
style D fill:#96ceb4
style E fill:#ffeaa7
style F fill:#dfe6e9
⚠️ 常见陷阱和误区
陷阱 1:IOC 和 DI 的关系
- ❌ 错误理解:IOC 和 DI 是同一个东西
- ✅ 正确理解:IOC(控制反转)是思想,DI(依赖注入)是实现方式
陷阱 2:@Autowired 和 @Resource 的区别
- ❌ 错误理解:它们可以随意替换
- ✅ 正确理解:
@Autowired按类型注入(Spring 原生)@Resource按名称注入(JSR-250 标准)
陷阱 3:Spring AOP 的代理方式
- ❌ 错误理解:Spring AOP 只用 JDK 动态代理
- ✅ 正确理解:
- 目标类有接口 → JDK 动态代理
- 目标类没有接口 → CGLIB 代理
陷阱 4:@Transactional 失效场景
- ❌ 错误理解:加了 @Transactional 就一定会回滚
- ✅ 正确理解:有很多失效场景(自调用、非 public 方法、异常类型不对)
陷阱 5:Spring Boot 自动配置原理
- ❌ 错误理解:Spring Boot 会自动配置所有东西
- ✅ 正确理解:Spring Boot 的自动配置是条件化的(@Conditional)
🎯 面试技巧
技巧 1:回答问题时,先讲”是什么”,再讲”为什么”
- ❌ 不好的回答:直接讲底层原理,面试官听不懂
- ✅ 好的回答:
- 先讲”是什么”(一句话总结)
- 再讲”为什么”(深度解析)
- 最后讲”实际应用场景”(面试加分回答)
技巧 2:用”比喻”帮助理解
- IOC:就像”不用自己 new 对象,而是找 Spring 要对象”
- AOP:就像”在不修改源码的情况下,给方法加功能”
- 动态代理:就像”中介代替房东出租房子”
技巧 3:结合”实际项目经验”回答
- 不要只背概念,要讲你在实际项目中怎么用的
- 比如:”我在 XX 项目中,用 Spring AOP 实现了日志记录…”
📖 推荐学习资源
官方文档(最权威)
书籍(深入理解)
- 《Spring 实战(第 5 版)》- 适合入门
- 《Spring 技术内幕》- 适合深入理解源码
- 《Spring Boot 编程思想》- 适合理解设计思想
视频教程(直观易懂)
- 尚硅谷 Spring 全套教程
- 黑马程序员 Spring 教程
- 动力节点 Spring 教程
💪 学习建议
- 不要死记硬背,要理解原理
- 多写代码,亲自体验 Spring 的各种特性
- 看源码,理解 Spring 的设计思想
- 做项目,在实际项目中应用 Spring
- 刷面试题,熟悉常见的面试问题
现在,让我们开始学习 Spring 吧!🚀
Spring 面试八股文 50 道|深度详解版
写给准备面试的你:
这篇文章不讲废话,每个知识点都从「是什么 → 为什么 → 怎么用」三个层次讲透。
配有大量比喻和场景化解释,目标是让没有 Spring 基础的人也能看懂。
建议配合实际代码练习,理解效果翻倍。
一、Spring Core 基础(1-10)
Q1:什么是 Spring?它的核心思想是什么?
一句话结论
Spring 是一个轻量级的 Java 开发框架,核心思想是 IOC(控制反转)和 AOP(面向切面编程)。
💻 代码示例:理解 IOC
传统方式(没有 IOC):
1 | |
IOC 方式(Spring):
1 | |
对比:
| 方式 | 优点 | 缺点 |
|---|---|---|
| 传统方式 | 简单直接 | 耦合度高,难以测试 |
| IOC 方式 | 解耦合,易于测试 | 需要学习 Spring |
⚠️ 常见错误
错误 1:认为 IOC 就是 DI
1 | |
错误 2:不知道 IOC 的好处
1 | |
🎯 面试场景模拟
面试官:”请讲讲什么是 Spring IOC?”
你的回答:
- 一句话总结:IOC(控制反转)就是将对象的创建和依赖注入交给 Spring 容器管理,而不是自己 new 对象。
- 举个例子:就像”不用自己去超市买菜,而是点外卖,让外卖小哥送过来”。
- 好处:解耦合、易于测试、集中管理。
- 实际项目中的应用:我在 XX 项目中,用 Spring IOC 管理了所有的 Service 和 Dao,避免了硬编码依赖。
深度解析
1. Spring 是什么?
用大白话解释:
Spring 就像一个智能工厂,你只需要告诉它你要什么(定义 Bean),它就会帮你创建对象、管理对象、组装对象,你不用自己 new。
传统 Java 开发 vs Spring 开发:
1 | |
2. IOC(控制反转)是什么?
IOC = Inversion of Control,翻译过来就是控制反转。
用大白话解释:
传统开发:你自己控制对象的创建(
UserDao userDao = new UserDaoImpl())
Spring IOC:Spring 容器控制对象的创建和注入(你只管声明,Spring 帮你 new)
3. AOP(面向切面编程)是什么?
AOP = Aspect-Oriented Programming,翻译过来就是面向切面编程。
用大白话解释:
你有很多方法都需要做同样的事情(比如日志、权限校验、事务管理),AOP 让你把这些横切逻辑抽出来,统一处理,不用每个方法都写一遍。
面试加分回答
「Spring 的核心思想是 IOC 和 AOP。IOC 让对象的创建和依赖管理交给 Spring 容器,降低了耦合;AOP 把日志、事务、权限等横切逻辑统一管理,提高了代码复用性。这两个核心思想支撑了整个 Spring 生态(Spring Boot、Spring MVC、Spring Cloud)。」
Q2:什么是 IOC(控制反转)?什么是 DI(依赖注入)?
一句话结论
IOC(控制反转)是思想,DI(依赖注入)是实现方式。 IOC 把对象创建权交给 Spring,DI 是 Spring 帮你把依赖注入进来。
💻 代码示例:理解 DI
构造函数注入(推荐):
1 | |
Setter 方法注入:
1 | |
字段注入(不推荐):
1 | |
为什么构造函数注入是最好的选择?
- 不可变性:依赖可以是
final的 - 可测试性:可以方便地传入 mock 对象
- 避免 NPE:依赖在创建对象时就确定了
⚠️ 常见错误
错误 1:混淆 IOC 和 DI
1 | |
错误 2:不知道 DI 的三种方式
1 | |
🎯 面试场景模拟
面试官:”请讲讲 IOC 和 DI 的区别?”
你的回答:
- IOC(控制反转):是一种设计思想,将对象的创建和依赖管理从应用程序代码转移到框架(Spring 容器)。
- DI(依赖注入):是 IOC 的具体实现方式,有三种:构造函数注入、Setter 方法注入、字段注入。
- 比喻:IOC 就像”不用自己做饭,而是点外卖”;DI 就像”外卖小哥把饭送到你手里”。
- 实际项目中的应用:我在 XX 项目中,主要使用构造函数注入,因为它可以保证依赖的不可变性,也便于单元测试。
深度解析
1. IOC(控制反转)详解
用大白话解释:
你去餐厅吃饭,传统的做法是你自己去厨房做(自己 new 对象);
IOC 的做法是你点菜,厨房帮你做(Spring 容器帮你创建对象)。
2. DI(依赖注入)详解
DI = Dependency Injection,翻译过来就是依赖注入。
用大白话解释:
你是一个 Service 类,你需要一个 Dao 类。传统做法是你自己去 new 一个 Dao;DI 的做法是Spring 帮你把 Dao 注入进来(你只管用,不管它怎么来的)。
3. DI 的三种方式
1 | |
面试加分回答
「IOC 是思想,DI 是实现方式。DI 有三种实现方式:构造器注入(推荐)、Setter 注入、字段注入(不推荐)。Spring 4.0 之后官方推荐使用构造器注入,因为它能保证依赖不可变(final),更利于测试。」
Q3:@Autowired 和 @Resource 有什么区别?
一句话结论
@Autowired 是 Spring 的注解,默认按类型注入;@Resource 是 JSR-250 规范,默认按名称注入。
深度解析
1. @Autowired(Spring 注解)
1 | |
2. @Resource(JSR-250 规范,Java 标准)
1 | |
3. 核心区别对比表
| 对比项 | @Autowired | @Resource |
|---|---|---|
| 来源 | Spring 注解(org.springframework.beans.factory.annotation.Autowired) |
JSR-250 规范(javax.annotation.Resource) |
| 默认注入方式 | 按类型(byType) | 按名称(byName) |
| 是否支持 @Primary | ✅ 支持 | ❌ 不支持 |
| 是否支持 @Qualifier | ✅ 支持 | ❌ 不支持 |
| 适用场景 | Spring 项目(推荐) | 希望代码和 Spring 解耦(不推荐) |
面试加分回答
「@Autowired 是 Spring 的注解,按类型注入,如果有多个实现需要配合 @Qualifier 或 @Primary 使用;@Resource 是 JSR-250 规范,按名称注入。实际项目中,推荐用 @Autowired + 构造器注入(Spring 官方推荐),因为它能保证依赖不可变,更利于测试。」
Q4:Spring 中的 Bean 是什么?@Component 和 @Bean 有什么区别?
一句话结论
Bean 是 Spring 容器中管理的对象。@Component 用在类上(自动扫描),@Bean 用在方法上(手动声明,适合第三方库)。
深度解析
1. Bean 是什么?
用大白话解释:
Bean 就是被 Spring 容器管理的对象。你自己 new 的对象不是 Bean,只有交给 Spring 管理的才是 Bean。
2. @Component(自动扫描)
1 | |
Spring 启动时会自动扫描 @Component、@Service、@Repository、@Controller 注解的类,自动注册为 Bean。
3. @Bean(手动声明)
1 | |
4. 核心区别对比表
| 对比项 | @Component | @Bean |
|---|---|---|
| 作用位置 | 类上 | 方法上 |
| 使用场景 | 自己写的类(可以加注解) | 第三方库(不能修改源码) |
| 实例化方式 | 自动扫描 + 反射创建 | 手动 new + 返回对象 |
| 示例 | @Service、@Repository |
@Bean 方法返回对象 |
面试加分回答
「@Component 和 @Bean 都是声明 Bean 的方式。@Component 用在自己写的类上(自动扫描),@Bean 用在方法上(手动声明),适合第三方库(比如 DataSource、RedisTemplate)。实际项目中,自己写的 Service/Controller 用 @Component(@Service/@Controller),第三方库用 @Bean。」
Q5:@Component、@Service、@Repository、@Controller 有什么区别?
一句话结论
这四个注解功能完全一样(都是注册 Bean),只是语义不同,方便分层和后续扩展。
深度解析
1. 四个注解的语义
1 | |
2. 为什么要有四个注解?
用大白话解释:
就像餐厅里,服务员、厨师、采购员都穿不同的制服(虽然都是员工),方便管理。
@Controller、@Service、@Repository、@Component 就是不同的”制服”,方便 Spring 识别分层,也方便后续扩展(比如 @Repository 能自动翻译数据库异常)。
面试加分回答
「这四个注解功能完全一样(都是通过 @Component 实现的),只是语义不同。@Controller 用于表现层,@Service 用于业务层,@Repository 用于持久层(还能自动翻译数据库异常),@Component 用于其他组件。实际项目中,严格按照分层使用对应的注解,代码更清晰。」
Q6:Spring 中的 Bean 作用域(Scope)有哪些?
一句话结论
Spring Bean 默认是单例(singleton),还有原型(prototype)、请求(request)、会话(session)等作用域。
深度解析
1. 六种作用域
| 作用域 | 说明 | 适用场景 |
|---|---|---|
singleton(默认) |
整个 Spring 容器只有一个实例 | Service、Dao(无状态,线程安全) |
prototype |
每次注入/获取都创建新实例 | 有状态的对象(每次都要新的) |
request |
每个 HTTP 请求一个实例 | Web 项目,一次请求内共享 |
session |
每个 HTTP 会话一个实例 | Web 项目,用户登录信息 |
application |
整个 ServletContext 一个实例 | 全局配置 |
websocket |
每个 WebSocket 会话一个实例 | WebSocket 连接 |
2. 代码示例
1 | |
3. 单例 Bean 的线程安全问题
单例 Bean 默认不是线程安全的! 如果有成员变量,多个线程修改会有并发问题。
1 | |
解决方案:
- 不用成员变量(无状态设计,推荐)
- 用
ThreadLocal - 用
synchronized(性能差)
面试加分回答
「Spring Bean 默认是单例(singleton),还有 prototype、request、session 等作用域。单例 Bean 不是线程安全的,如果有成员变量会有并发问题。实际项目中,Service 和 Dao 一般都是无状态的(没有成员变量),所以是线程安全的。如果有状态需求,可以用 prototype 作用域。」
Q7:Spring 中的 Bean 生命周期有哪些阶段?
一句话结论
Bean 生命周期分为 5 个阶段:实例化 → 属性赋值 → 初始化 → 使用 → 销毁。
深度解析
1. Bean 生命周期流程图
1 | |
2. 代码示例
1 | |
3. 面试常问:三级缓存解决循环依赖
Spring 用三级缓存解决循环依赖(A 依赖 B,B 依赖 A):
1 | |
面试加分回答
「Bean 生命周期有 5 个阶段:实例化 → 属性赋值 → 初始化 → 使用 → 销毁。其中初始化阶段有很多扩展点(@PostConstruct、InitializingBean、init-method)。另外,Spring 用三级缓存解决循环依赖,这是面试超高频题,需要重点掌握。」
Q8:Spring 中什么是循环依赖?怎么解决?
一句话结论
循环依赖就是 A 依赖 B,B 依赖 A。Spring 用三级缓存解决单例 Bean 的循环依赖,但原型 Bean 无法解决。
深度解析
1. 什么是循环依赖?
1 | |
2. Spring 怎么解决循环依赖?(三级缓存)
用大白话解释:
就像两个人互相给对方做担保:
- A 先实例化(创建一个空对象,属性还没赋值),放进”半成品缓存”
- A 发现需要 B,去创建 B
- B 实例化,发现需要 A,从”半成品缓存”拿到 A 的半成品(虽然属性没赋值完,但可以用)
- B 初始化完成,放进”成品缓存”
- A 拿到 B 的成品,完成属性赋值,初始化完成,放进”成品缓存”
3. 三级缓存详解
1 | |
4. 什么情况无法解决循环依赖?
- 原型 Bean(prototype):每次都创建新实例,无法缓存
- 构造函数注入:构造时就需要依赖,无法先创建”半成品”
面试加分回答
「Spring 用三级缓存解决单例 Bean 的循环依赖。但如果用构造函数注入,会报
BeanCurrentlyInCreationException,因为构造时就需要依赖,无法先创建半成品。解决方式:1) 用 @Lazy 延迟加载;2) 改用字段注入或 Setter 注入;3) 重构代码,打破循环依赖(最优解)。」
Q9:什么是 AOP?有哪些应用场景?
一句话结论
AOP(面向切面编程)是把日志、事务、权限等横切逻辑统一管理,不用每个方法都写一遍。
深度解析
1. AOP 是什么?
用大白话解释:
你有很多方法都需要做同样的事情(比如日志、权限校验、事务管理),AOP 让你把这些横切逻辑抽出来,统一处理,不用每个方法都写一遍。
2. AOP 核心概念
| 概念 | 说明 | 示例 |
|---|---|---|
| 切面(Aspect) | 横切逻辑的集合 | 日志切面、事务切面 |
| 连接点(Join Point) | 可以被拦截的方法 | 所有方法 |
| 切入点(Pointcut) | 要拦截哪些方法 | @Pointcut("execution(* com.example.*.*(..))") |
| 通知(Advice) | 拦截后做什么 | 前置通知、后置通知、环绕通知 |
| 目标对象(Target) | 被代理的对象 | 原始 Service |
| 代理对象(Proxy) | 增强后的对象 | Spring 用 JDK 动态代理或 CGLIB 代理 |
3. 代码示例
1 | |
面试加分回答
「AOP 是面向切面编程,把日志、事务、权限等横切逻辑统一管理。Spring AOP 用 JDK 动态代理(接口)或 CGLIB 代理(类)。实际项目中,事务管理(@Transactional)就是用 AOP 实现的。另外,AOP 和 AspectJ 的区别是:Spring AOP 是运行时增强(动态代理),AspectJ 是编译时增强(性能更好)。」
Q10:Spring AOP 中的动态代理有哪些?JDK 动态代理和 CGLIB 代理的区别?
一句话结论
Spring AOP 用 JDK 动态代理(有接口时用)和 CGLIB 代理(没接口时用)。JDK 动态代理基于接口,CGLIB 基于继承。
深度解析
1. JDK 动态代理(有接口时用)
1 | |
2. CGLIB 代理(没接口时用)
1 | |
3. 核心区别对比表
| 对比项 | JDK 动态代理 | CGLIB 代理 |
|---|---|---|
| 原理 | 基于接口(实现接口) | 基于继承(生成子类) |
| 性能 | JDK 8 之后性能接近 CGLIB | 第一次生成慢,后续调用快 |
| 限制 | 必须有接口 | 不能被 final 修饰 |
| Spring 选择策略 | 有接口 → JDK 动态代理 | 没接口 → CGLIB 代理 |
面试加分回答
「Spring AOP 用 JDK 动态代理(有接口时)和 CGLIB 代理(没接口时)。JDK 8 之后性能接近,CGLIB 第一次生成慢但后续调用快。Spring Boot 2.x 之后,默认用 CGLIB 代理(设置
spring.aop.proxy-target-class=true)。另外,CGLIB 不能代理 final 类和方法。」
二、Spring MVC 与 Web 开发(11-15)
Q11:Spring MVC 的工作流程是什么?
一句话结论
Spring MVC 的工作流程:请求 → DispatcherServlet → HandlerMapping → Controller → Service → DAO → 返回视图/JSON。
深度解析
1. Spring MVC 工作流程图
1 | |
2. 核心组件详解
| 组件 | 作用 |
|---|---|
DispatcherServlet |
前端控制器,统一接收请求(入口) |
HandlerMapping |
处理器映射器,根据 URL 找到对应的 Controller |
HandlerAdapter |
处理器适配器,执行 Controller 方法 |
Controller |
处理器,执行业务逻辑 |
ViewResolver |
视图解析器,解析视图名称(比如返回 “user/list” → /WEB-INF/views/user/list.jsp) |
3. 代码示例
1 | |
面试加分回答
「Spring MVC 的核心是 DispatcherServlet(前端控制器),所有请求都经过它,然后分发给对应的 Controller。这个流程和餐厅点菜很像:DispatcherServlet 是前台,HandlerMapping 是菜单(找菜品),Controller 是厨师(做菜)。另外,现在主流是前后端分离,Controller 返回 JSON(@ResponseBody 或 @RestController),不再返回视图。」
Q12:@RequestParam、@PathVariable、@RequestBody 有什么区别?
一句话结论
@RequestParam 获取查询参数,@PathVariable 获取路径参数,@RequestBody 获取请求体(JSON)。
深度解析
1. @RequestParam(获取查询参数)
1 | |
2. @PathVariable(获取路径参数)
1 | |
3. @RequestBody(获取请求体,JSON)
1 | |
4. 核心区别对比表
| 注解 | 数据来源 | 示例 |
|---|---|---|
@RequestParam |
URL 查询参数(?name=张三) |
/users?name=张三 |
@PathVariable |
URL 路径参数(/users/123) |
/users/123 |
@RequestBody |
请求体(JSON、XML) | POST /users,请求体 {"name":"张三"} |
面试加分回答
「@RequestParam 获取 URL 查询参数(?name=张三),@PathVariable 获取 URL 路径参数(/users/123),@RequestBody 获取请求体(JSON)。实际项目中,GET 请求用 @RequestParam 或 @PathVariable,POST/PUT 请求用 @RequestBody 接收 JSON。另外,@RequestBody 需要和
Content-Type: application/json配合使用。」
Q13:拦截器(Interceptor)和过滤器(Filter)的区别?
一句话结论
Filter 是 Servlet 规范,拦截所有 HTTP 请求;Interceptor 是 Spring MVC 规范,只拦截进入 Controller 的请求。执行顺序是:Filter → Interceptor → Controller。
深度解析
1. 执行顺序图
1 | |
2. 代码示例
1 | |
3. 核心区别对比表
| 对比项 | Filter | Interceptor |
|---|---|---|
| 规范 | Servlet 规范(Java EE) | Spring MVC 规范 |
| 拦截范围 | 所有 HTTP 请求(包括静态资源) | 只拦截进入 Controller 的请求 |
| 注入 Spring Bean | 不能直接注入 | 可以直接注入(是 Spring 管理的 Bean) |
| 执行时机 | 最外层(最早执行,最晚结束) | DispatcherServlet 内部(Controller 前后) |
面试加分回答
「Filter 和 Interceptor 的核心区别是规范不同、拦截范围不同。Filter 是 Servlet 规范,拦截所有请求;Interceptor 是 Spring MVC 规范,只拦截 Controller 请求。实际项目中,登录校验、权限校验用 Interceptor 更方便(可以直接注入 TokenService 等 Bean),而编码转换、CORS 处理用 Filter 更合适(在 Spring 之前执行)。」
Q14:Spring MVC 中 @Controller 和 @RestController 有什么区别?
一句话结论
@Controller 返回视图(HTML),@RestController = @Controller + @ResponseBody,直接返回 JSON。
深度解析
1. @Controller(返回视图)
1 | |
2. @RestController(返回 JSON,前后端分离)
1 | |
3. 核心区别对比表
| 对比项 | @Controller | @RestController |
|---|---|---|
| 返回值 | 视图名称(HTML) | JSON / XML |
| 是否需要 @ResponseBody | 需要(如果返回 JSON) | 不需要(已经包含) |
| 适用场景 | 传统的服务端渲染(Thymeleaf、JSP) | 前后端分离(返回 JSON) |
面试加分回答
「@Controller 返回视图(HTML),适合传统的服务端渲染;@RestController = @Controller + @ResponseBody,直接返回 JSON,适合前后端分离。现在主流是前后端分离,所以用 @RestController 更多。另外,@ResponseBody 的作用是把返回值序列化为 JSON(通过 HttpMessageConverter)。」
Q15:Spring MVC 中如何统一处理异常?
一句话结论
用 @ControllerAdvice + @ExceptionHandler 统一处理异常,不用在每个 Controller 里写 try-catch。
深度解析
1. 传统方式(不推荐)
1 | |
2. 统一异常处理(推荐)
1 | |
3. 使用示例
1 | |
面试加分回答
「统一异常处理用 @ControllerAdvice + @ExceptionHandler,可以拦截所有 Controller 抛出的异常,返回统一的错误格式(比如
{code: 404, msg: "用户不存在"})。这样不用在每个 Controller 里写 try-catch,代码更简洁。另外,Spring Boot 也提供了ErrorController来处理全局错误,但 @ControllerAdvice 更灵活。」
三、Spring Boot 核心(16-25)
Q16:什么是 Spring Boot?它和 Spring 有什么区别?
一句话结论
Spring Boot 是 Spring 的”快速开发版”,通过自动配置和起步依赖,让你不用写大量 XML 配置。
深度解析
1. Spring 和 Spring Boot 的核心区别
用大白话解释:
Spring:像一个功能强大的厨房,但你需要自己准备所有厨具、食材、调料(配置 Bean、配置数据源、配置事务……)
Spring Boot:像一个有智能管家的厨房,厨具、食材、调料都帮你准备好了,你只需要直接炒菜(写业务代码)
2. Spring Boot 的核心特性
| 特性 | 说明 |
|---|---|
| 自动配置(Auto Configuration) | 根据你引入的依赖,自动配置 Bean(比如引入 spring-boot-starter-data-redis,自动配置 RedisTemplate) |
| 起步依赖(Starter) | 把相关依赖打包成一个(比如 spring-boot-starter-web 包含了 Spring MVC、Jackson、Tomcat) |
| 内嵌 Servlet 容器 | 内置 Tomcat、Jetty、Undertow,不用部署 WAR 包 |
| Actuator 监控 | 提供健康检查、指标监控等开箱即用的端点 |
| 命令行接口(CLI) | 可以快速运行 Groovy 脚本 |
3. 对比示例
1 | |
面试加分回答
「Spring Boot 的核心是自动配置和起步依赖。自动配置通过
@EnableAutoConfiguration实现(实际上 @SpringBootApplication 已经包含了),它会根据你引入的依赖自动配置 Bean。起步依赖是把相关依赖打包成一个(比如 spring-boot-starter-web 包含了 Spring MVC、Jackson、Tomcat),简化依赖管理。另外,Spring Boot 的内嵌容器让你可以用java -jar直接运行,不用部署到外部 Tomcat。」
Q17:Spring Boot 的自动配置原理是什么?
一句话结论
Spring Boot 自动配置通过 @EnableAutoConfiguration 实现,核心是 SpringFactoriesLoader 加载 META-INF/spring.factories 中的自动配置类。
深度解析
1. 自动配置流程
1 | |
2. 核心注解 @SpringBootApplication
1 | |
3. 自动配置类示例(RedisAutoConfiguration)
1 | |
4. 条件注解(自动配置的灵魂)
| 条件注解 | 说明 |
|---|---|
@ConditionalOnClass |
类路径有指定类才生效 |
@ConditionalOnMissingBean |
容器中没有指定 Bean 才生效 |
@ConditionalOnBean |
容器中有指定 Bean 才生效 |
@ConditionalOnProperty |
配置文件中指定属性有指定值才生效 |
@ConditionalOnWebApplication |
是 Web 应用才生效 |
面试加分回答
「Spring Boot 自动配置的核心是 @EnableAutoConfiguration,它会通过 SpringFactoriesLoader 加载所有 jar 包中的 META-INF/spring.factories 文件,读取其中的自动配置类(XxxAutoConfiguration),然后根据条件注解(@ConditionalOnClass、@ConditionalOnMissingBean 等)决定是否生效。如果你想自定义配置,只需要自己定义一个 Bean,因为 @ConditionalOnMissingBean 会让你定义的 Bean 优先。」
Q18:Spring Boot 的启动流程是怎样的?
一句话结论
Spring Boot 启动流程:加载配置 → 创建 ApplicationContext → 刷新上下文(自动配置、Bean 扫描、启动 Tomcat)→ 启动完成。
深度解析
1. 启动流程详解
1 | |
启动流程(简化版):
1 | |
2. 核心代码(SpringApplication.run())
1 | |
面试加分回答
「Spring Boot 启动流程的核心是 SpringApplication.run(),它会创建 ApplicationContext、刷新上下文(自动配置、Bean 扫描、启动内嵌 Tomcat)、调用 Runner。面试中经常问:’Spring Boot 启动后做了什么?’ 回答要包括:1) 加载自动配置;2) 扫描 Bean;3) 启动内嵌 Tomcat;4) 调用 Runner。」
Q19:Spring Boot 中的配置文件有哪些?优先级怎样?
一句话结论
Spring Boot 配置文件有 application.properties 和 application.yml,优先级:命令行参数 > 外部配置文件 > 内部配置文件 > 默认值。
深度解析
1. 配置文件格式
1 | |
1 | |
2. 配置优先级(从高到低)
| 优先级 | 位置 | 说明 |
|---|---|---|
| 1 | 命令行参数(--server.port=8081) |
最高 |
| 2 | 外部配置文件(file:./config/) |
项目根目录下的 config 文件夹 |
| 3 | 外部配置文件(file:./) |
项目根目录 |
| 4 | 内部配置文件(classpath:/config/) |
resources/config 文件夹 |
| 5 | 内部配置文件(classpath:/) |
resources 目录(默认) |
3. 多环境配置(Profile)
1 | |
面试加分回答
「Spring Boot 配置文件优先级:命令行参数 > 外部配置文件(file:./config/)> 外部配置文件(file:./)> 内部配置文件(classpath:/config/)> 内部配置文件(classpath:/)。多环境配置用 spring.profiles.active 指定,配置文件名是 application-{profile}.yml。另外,配置绑定用 @ConfigurationProperties(推荐,类型安全)或 @Value(不推荐,功能弱)。」
Q20:@ConfigurationProperties 和 @Value 有什么区别?
一句话结论
@ConfigurationProperties 批量绑定配置(类型安全,支持校验),@Value 单个绑定配置(功能弱,不支持校验)。
深度解析
1. @ConfigurationProperties(推荐)
1 | |
1 | |
2. @Value(不推荐)
1 | |
3. 核心区别对比表
| 对比项 | @ConfigurationProperties | @Value |
|---|---|---|
| 功能 | 批量绑定(支持复杂类型) | 单个绑定(只支持简单类型) |
| 类型安全 | ✅ 类型安全(编译期检查) | ❌ 类型不安全(运行期才报错) |
| 校验支持 | ✅ 支持 @Validated | ❌ 不支持 |
| 松散绑定 | ✅ 支持(user-name 或 userName 都能识别) | ❌ 不支持 |
| 推荐程度 | ✅ 推荐 | ❌ 不推荐 |
面试加分回答
「@ConfigurationProperties 和 @Value 都是绑定配置的方式,但 @ConfigurationProperties 更强大:1) 支持批量绑定;2) 类型安全;3) 支持校验(@Validated);4) 支持松散绑定(user-name 或 userName 都能识别)。实际项目中,推荐用 @ConfigurationProperties 绑定配置。」
Q21:Spring Boot 中的 Starter 是什么?如何自定义 Starter?
一句话结论
Starter 是把相关依赖打包成一个依赖(比如 spring-boot-starter-web 包含了 Spring MVC、Jackson、Tomcat)。自定义 Starter 需要创建自动配置类和 META-INF/spring.factories。
深度解析
1. 什么是 Starter?
用大白话解释:
Starter 就像一个”工具箱”,你引入一个 Starter,就自动获得了所有相关依赖。
比如spring-boot-starter-web,你引入它,就自动获得了 Spring MVC、Jackson、Tomcat,不用一个个引依赖。
2. 常用 Starter
| Starter | 说明 |
|---|---|
spring-boot-starter-web |
Spring MVC + 内嵌 Tomcat + Jackson |
spring-boot-starter-data-redis |
Redis 客户端 + RedisTemplate |
spring-boot-starter-data-jpa |
JPA + Hibernate |
spring-boot-starter-security |
Spring Security |
spring-boot-starter-test |
JUnit 5 + Mockito + Spring Test |
3. 自定义 Starter
1 | |
面试加分回答
「Starter 是 Spring Boot 的依赖打包机制,通过自动配置实现’开箱即用’。自定义 Starter 需要三步:1) 创建自动配置类(@Configuration + 条件注解);2) 创建属性配置类(@ConfigurationProperties);3) 创建 META-INF/spring.factories(Spring Boot 2.7 之前)或 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring Boot 2.7 之后)。」
Q22:Spring Boot 中的 Actuator 是做什么的?有哪些常用端点?
一句话结论
Actuator 是 Spring Boot 的监控神器,提供了很多开箱即用的端点(Endpoint),可以查看应用的健康状态、配置信息、Bean 信息、内存使用情况等。
深度解析
1. 常用端点(Endpoint)
| 端点 | 说明 | 是否默认开启 |
|---|---|---|
/actuator/health |
应用健康状态(磁盘空间、数据库、Redis 等) | ✅ 开启 |
/actuator/info |
应用自定义信息(版本号、描述等) | ✅ 开启 |
/actuator/beans |
容器中所有的 Bean | ❌ 关闭 |
/actuator/env |
环境变量和配置文件属性 | ❌ 关闭 |
/actuator/mappings |
所有的 @RequestMapping 路径 | ❌ 关闭 |
/actuator/metrics |
性能指标(JVM 内存、GC、线程池等) | ❌ 关闭 |
/actuator/threaddump |
线程快照(类似 jstack) | ❌ 关闭 |
/actuator/heapdump |
堆转储文件(类似 jmap) | ❌ 关闭 |
2. 启用方式
1 | |
3. 自定义健康检查
1 | |
面试加分回答
「Actuator 是 Spring Boot 的监控神器,提供了很多开箱即用的端点。生产环境中,一定要配置访问权限(不能让所有人都能访问 /actuator/heapdump),并且只暴露必要的端点(比如 health、info)。另外,Actuator 可以和 Prometheus + Grafana 集成,实现可视化监控。」
Q23:Spring Boot 中如何实现全局异常处理?
一句话结论
用 @ControllerAdvice + @ExceptionHandler 统一处理异常,不用在每个 Controller 里写 try-catch。
深度解析
1. 统一异常处理示例
1 | |
2. 自定义异常
1 | |
面试加分回答
「全局异常处理用 @ControllerAdvice + @ExceptionHandler,可以拦截所有 Controller 抛出的异常,返回统一的错误格式。这样不用在每个 Controller 里写 try-catch,代码更简洁。另外,@ControllerAdvice 还可以做全局数据绑定(@InitBinder)和全局数据预处理(@ModelAttribute)。」
Q24:Spring Boot 中的事务管理是怎么实现的?
一句话结论
Spring Boot 事务管理通过 @Transactional 注解实现,底层是 AOP(动态代理)。
深度解析
1. @Transactional 使用示例
1 | |
2. 事务传播机制
| 传播机制 | 说明 |
|---|---|
REQUIRED(默认) |
如果当前有事务,就加入;如果没有,就新建 |
REQUIRES_NEW |
挂起当前事务,新建一个事务 |
SUPPORTS |
如果当前有事务,就加入;如果没有,就以非事务方式执行 |
NOT_SUPPORTED |
以非事务方式执行,如果当前有事务,就挂起 |
MANDATORY |
如果当前有事务,就加入;如果没有,就抛异常 |
NEVER |
以非事务方式执行,如果当前有事务,就抛异常 |
NESTED |
如果当前有事务,就创建嵌套事务;如果没有,就新建 |
3. 事务隔离级别
| 隔离级别 | 说明 |
|---|---|
DEFAULT(默认) |
使用数据库的默认隔离级别 |
READ_UNCOMMITTED |
读未提交(脏读、不可重复读、幻读) |
READ_COMMITTED |
读已提交(不可重复读、幻读) |
REPEATABLE_READ |
可重复读(幻读) |
SERIALIZABLE |
串行化(无问题,但性能差) |
面试加分回答
「@Transactional 底层是 AOP(动态代理),所以自调用会失效(同一个类中的方法调用另一个 @Transactional 方法,事务不生效)。解决方式:1) 用 AspectJ 替代 Spring AOP;2) 把方法放到另一个 Service;3) 用 AopContext.currentProxy() 获取当前代理对象。另外,@Transactional 的 rollbackFor 属性默认只对 RuntimeException 和 Error 回滚,所以建议显式指定 rollbackFor = Exception.class。」
Q25:Spring Boot 中 @Transactional 注解失效的常见场景有哪些?
一句话结论
@Transactional 失效的常见场景:自调用、非 public 方法、异常被 catch、数据库引擎不支持事务、没有配置事务管理器。
深度解析
1. 自调用(最常见)
1 | |
2. 非 public 方法
1 | |
3. 异常被 catch
1 | |
4. 数据库引擎不支持事务
1 | |
面试加分回答
「@Transactional 失效的常见场景:1) 自调用(同一个类中的方法调用另一个 @Transactional 方法);2) 非 public 方法;3) 异常被 catch;4) 数据库引擎不支持事务(比如 MyISAM);5) 没有配置事务管理器;6) 异常类型不对(默认只对 RuntimeException 和 Error 回滚)。其中自调用是最常见的坑,解决方式是把方法放到另一个 Service,或者用 AopContext.currentProxy() 获取当前代理对象。」
四、Spring Security(26-30)
Q26:Spring Security 的核心组件有哪些?
一句话结论
Spring Security 的核心组件:SecurityContextHolder、Authentication、UserDetailsService、PasswordEncoder。
深度解析
1. 核心组件详解
| 组件 | 说明 |
|---|---|
SecurityContextHolder |
存储当前用户的认证信息(SecurityContext) |
SecurityContext |
用户的认证信息(Authentication) |
Authentication |
认证信息(用户名、密码、权限) |
UserDetailsService |
根据用户名查用户(你需要实现) |
PasswordEncoder |
密码加密和校验 |
2. 认证流程
1 | |
3. 代码示例
1 | |
面试加分回答
「Spring Security 的核心组件:SecurityContextHolder(存储认证信息)、Authentication(认证信息)、UserDetailsService(查用户)、PasswordEncoder(密码加密)。认证流程是:用户登录 → UsernamePasswordAuthenticationFilter → AuthenticationManager → AuthenticationProvider → UserDetailsService → 认证成功/失败。另外,现在主流的认证方式是 JWT + Spring Security,JWT 过滤器(OncePerRequestFilter)会在每次请求时解析 Token,将认证信息放入 SecurityContextHolder。」
Q27:Spring Security 中 JWT 是怎么集成的?
一句话结论
JWT 集成需要:1) 登录成功后生成 JWT;2) 自定义过滤器(OncePerRequestFilter)解析 JWT;3) 将认证信息放入 SecurityContextHolder。
深度解析
1. JWT 登录流程
1 | |
2. 代码示例
1 | |
面试加分回答
「JWT 集成需要三步:1) 登录成功后生成 JWT;2) 自定义过滤器(OncePerRequestFilter)解析 JWT;3) 将认证信息放入 SecurityContextHolder。JWT 的优点是无状态(不用存 Session),缺点是无法主动失效(只能等过期)。实际项目中,一般会配合 Redis 实现 JWT 黑名单(用户退出登录时,把 JWT 加入黑名单)。」
Q28:Spring Security 中如何自定义登录成功/失败处理?
一句话结论
自定义登录成功/失败处理需要:1) 实现 AuthenticationSuccessHandler/AuthenticationFailureHandler;2) 在 SecurityConfig 中配置。
深度解析
1. 自定义登录成功处理
1 | |
2. 自定义登录失败处理
1 | |
3. 在 SecurityConfig 中配置
1 | |
面试加分回答
「自定义登录成功/失败处理需要实现 AuthenticationSuccessHandler/AuthenticationFailureHandler,然后在 SecurityConfig 中配置。实际项目中,前后端分离一般用 JSON 返回(而不是跳转页面),所以需要自定义成功/失败处理。另外,Spring Security 默认登录成功会跳转,前后端分离需要返回 JSON。」
Q29:Spring Security 中如何自定义权限校验?
一句话结论
自定义权限校验需要:1) 实现 AccessDecisionManager 或 @PreAuthorize;2) 在方法上用 @PreAuthorize 指定权限要求。
深度解析
1. 方法级权限控制(推荐,用 @PreAuthorize)
1 | |
2. 开启方法级权限控制
1 | |
3. 自定义权限校验逻辑
1 | |
面试加分回答
「Spring Security 的权限校验有 2 种方式:1) URL 级权限控制(在 SecurityConfig 中配置 .antMatchers(“/admin/**”).hasRole(“ADMIN”));2) 方法级权限控制(用 @PreAuthorize,更灵活)。实际项目中,推荐用方法级权限控制(@PreAuthorize),因为它更灵活,可以在方法上精确控制权限。」
五、Spring Cloud 微服务(30-35)
Q30:什么是 Spring Cloud?它和 Spring Boot 有什么关系?
一句话结论
Spring Cloud 是微服务架构的一站式解决方案,基于 Spring Boot 提供服务注册、配置管理、负载均衡、熔断器等功能。
深度解析
1. Spring Boot 和 Spring Cloud 的关系
用大白话解释:
Spring Boot:快速开发单个微服务(就像造一辆车)
Spring Cloud:管理多个微服务(就像管理一个车队,包括交通规则、加油站、维修站等)
2. Spring Cloud 核心组件
| 组件 | 说明 | 替代方案 |
|---|---|---|
| Eureka | 服务注册与发现 | Nacos、Consul |
| Ribbon | 客户端负载均衡(已停更) | Spring Cloud LoadBalancer |
| Feign | 声明式 HTTP 客户端(已停更) | OpenFeign |
| Hystrix | 熔断器(已停更) | Resilience4j、Sentinel |
| Zuul | API 网关(已停更) | Spring Cloud Gateway |
| Config | 分布式配置中心 | Nacos、Apollo |
| Bus | 消息总线(配置热更新) | Spring Cloud Bus |
3. 微服务架构图
1 | |
面试加分回答
「Spring Cloud 是微服务架构的一站式解决方案,基于 Spring Boot 提供服务注册、配置管理、负载均衡、熔断器等功能。但是,Spring Cloud Netflix 的很多组件已经停更(Eureka、Ribbon、Hystrix、Zuul),现在主流用 Spring Cloud Alibaba(Nacos、Sentinel、Dubbo)。另外,Spring Cloud 和 Dubbo 的区别是:Spring Cloud 是全家桶(包含各种组件),Dubbo 只是 RPC 框架。」
Q31:什么是服务注册与发现?Eureka 的工作原理是什么?
一句话结论
服务注册与发现是让服务自动注册自己的地址,并且能发现其他服务的地址。Eureka 分为 Eureka Server(注册中心)和 Eureka Client(服务)。
深度解析
1. 为什么需要服务注册与发现?
用大白话解释:
没有服务注册与发现:服务 A 调用服务 B,需要硬编码服务 B 的 IP 和端口(比如
http://192.168.1.100:8080),如果服务 B 扩容或下线,服务 A 需要手动修改配置。
有了服务注册与发现:服务 B 启动后自动注册到注册中心,服务 A 从注册中心自动获取服务 B 的地址,不需要硬编码。
2. Eureka 架构
1 | |
3. Eureka 核心概念
| 概念 | 说明 |
|---|---|
| 服务注册 | 服务启动时,向 Eureka Server 注册自己的信息(IP、端口、服务名) |
| 服务续约 | 服务每隔 30 秒向 Eureka Server 发送心跳,证明自己还活着 |
| 服务下线 | 服务关闭时,向 Eureka Server 发送下线请求 |
| 服务拉取 | 服务每隔 30 秒从 Eureka Server 拉取服务列表(缓存本地) |
| 自我保护机制 | 如果短时间内大量服务心跳丢失,Eureka Server 进入自我保护模式(不会剔除服务) |
面试加分回答
「Eureka 的核心是服务注册与发现。服务启动时向 Eureka Server 注册,每隔 30 秒发送心跳;服务调用时从 Eureka Server 拉取服务列表(缓存本地)。Eureka 的自我保护机制是:如果 15 分钟内超过 85% 的服务都心跳丢失,Eureka Server 会进入自我保护模式(不会剔除任何服务),防止因为网络分区导致服务被误剔除。另外,Eureka 已经停更,现在主流用 Nacos(Spring Cloud Alibaba)。」
Q32:什么是 Ribbon?它和 LoadBalancer 有什么区别?
一句话结论
Ribbon 是客户端负载均衡组件(已停更),Spring Cloud LoadBalancer 是它的替代方案。
深度解析
1. 什么是客户端负载均衡?
用大白话解释:
服务端负载均衡(比如 Nginx):客户端只认识 Nginx,Nginx 帮你转发到后端服务器。
客户端负载均衡(比如 Ribbon):客户端自己知道所有后端服务器的地址,自己选一个服务器调用。
2. Ribbon 使用示例
1 | |
3. Ribbon vs LoadBalancer
| 对比项 | Ribbon | Spring Cloud LoadBalancer |
|---|---|---|
| 状态 | ❌ 已停更 | ✅ 活跃维护 |
| 负载均衡策略 | 支持多种(轮询、随机、重试) | 只支持轮询、随机 |
| 扩展性 | 好 | 好 |
| 推荐程度 | ❌ 不推荐 | ✅ 推荐 |
面试加分回答
「Ribbon 是客户端负载均衡组件(已停更),Spring Cloud LoadBalancer 是它的替代方案。客户端负载均衡的意思是:客户端自己知道所有后端服务器的地址,自己选一个调用。另外,现在主流是 Spring Cloud Alibaba Sentinel(负载均衡 + 熔断),而不是单独的 LoadBalancer。」
Q33:什么是 Feign?它和 RestTemplate 有什么区别?
一句话结论
Feign 是声明式 HTTP 客户端(接口 + 注解),RestTemplate 是指令式 HTTP 客户端(手动调用)。
深度解析
1. RestTemplate(指令式)
1 | |
2. Feign(声明式,推荐)
1 | |
3. 核心区别对比表
| 对比项 | RestTemplate | Feign |
|---|---|---|
| 调用方式 | 指令式(手动拼接 URL) | 声明式(接口 + 注解) |
| 代码简洁度 | 差(需要手动拼接) | 好(像调用本地方法) |
| 负载均衡 | 需要 @LoadBalanced | 内置支持 |
| 推荐程度 | ❌ 不推荐 | ✅ 推荐 |
面试加分回答
「Feign 是声明式 HTTP 客户端,通过接口 + 注解定义 HTTP 请求,使用起来像调用本地方法。RestTemplate 是指令式 HTTP 客户端,需要手动拼接 URL。实际项目中,推荐用 Feign(或 OpenFeign),因为代码更简洁。另外,Feign 集成了 Ribbon(负载均衡)和 Hystrix(熔断),是一站式解决方案。」
Q34:什么是 Hystrix?它和 Sentinel 有什么区别?
一句话结论
Hystrix 是熔断器组件(已停更),Sentinel 是 Alibaba 的流量防卫兵(活跃维护,功能更强)。
深度解析
1. 为什么需要熔断器?
用大白话解释:
服务 A 调用服务 B,如果服务 B 挂了,服务 A 会一直等待(线程池耗尽),导致服务 A 也挂了(雪崩效应)。
熔断器的作用:如果服务 B 挂了,服务 A 直接快速失败(不去等待),并且降级处理(返回默认值)。
2. Hystrix 使用示例
1 | |
3. Hystrix vs Sentinel
| 对比项 | Hystrix | Sentinel |
|---|---|---|
| 状态 | ❌ 已停更 | ✅ 活跃维护 |
| 功能 | 熔断、降级 | 熔断、降级、限流、热点参数限流 |
| 配置方式 | 代码配置 | 控制台配置(动态配置) |
| 推荐程度 | ❌ 不推荐 | ✅ 推荐 |
面试加分回答
「Hystrix 是熔断器组件(已停更),Sentinel 是 Alibaba 的流量防卫兵(活跃维护)。Sentinel 比 Hystrix 功能更强:1) 支持限流(QPS 限流、线程数限流);2) 支持热点参数限流(比如某个参数值访问量太高,单独限流);3) 有控制台(可以动态配置规则)。实际项目中,推荐用 Sentinel(Spring Cloud Alibaba)。」
Q35:什么是 Spring Cloud Gateway?它和 Zuul 有什么区别?
一句话结论
Spring Cloud Gateway 是 API 网关(基于 WebFlux,非阻塞),Zuul 是 Netflix 的 API 网关(Zuul 1 是阻塞的,Zuul 2 是非阻塞但已停更)。
深度解析
1. 什么是 API 网关?
用大白话解释:
API 网关是所有请求的入口,负责:路由转发、负载均衡、权限校验、限流、日志监控等。
就像餐厅的前台,所有客人(请求)都要先到前台(网关),前台决定你要去哪个包间(服务)。
2. Spring Cloud Gateway 配置示例
1 | |
3. Gateway vs Zuul
| 对比项 | Spring Cloud Gateway | Zuul 1 | Zuul 2 |
|---|---|---|---|
| 底层 | WebFlux(非阻塞) | Servlet(阻塞) | WebFlux(非阻塞) |
| 性能 | 高 | 低 | 高 |
| 状态 | ✅ 活跃维护 | ❌ 停更 | ❌ 停更 |
| 推荐程度 | ✅ 推荐 | ❌ 不推荐 | ❌ 不推荐 |
面试加分回答
「Spring Cloud Gateway 是 API 网关(基于 WebFlux,非阻塞),负责路由转发、负载均衡、权限校验、限流等。Zuul 1 是阻塞的(性能差),Zuul 2 是非阻塞但已停更。实际项目中,推荐用 Spring Cloud Gateway 或 Nginx + Sentinel(限流)。另外,Gateway 的核心是 Route(路由)、Predicate(断言)、Filter(过滤器)。」
六、高频面试题补充(36-50)
Q36:Spring 中常见的设计模式有哪些?
一句话结论
Spring 中常见的设计模式:工厂模式、单例模式、代理模式、模板方法模式、观察者模式、适配器模式。
深度解析
1. 工厂模式(Factory Pattern)
1 | |
2. 单例模式(Singleton Pattern)
1 | |
3. 代理模式(Proxy Pattern)
1 | |
4. 模板方法模式(Template Method Pattern)
1 | |
5. 观察者模式(Observer Pattern)
1 | |
面试加分回答
「Spring 中常见的设计模式:1) 工厂模式(BeanFactory);2) 单例模式(Spring Bean 默认是单例);3) 代理模式(AOP 动态代理);4) 模板方法模式(JdbcTemplate、RestTemplate);5) 观察者模式(Spring 事件机制);6) 适配器模式(HandlerAdapter)。面试中经常问:’Spring 用到了哪些设计模式?’ 回答要具体(比如 AOP 用代理模式,事件机制用观察者模式)。」
Q37:Spring 中的事件机制(ApplicationEvent)是怎么用的?
一句话结论
Spring 事件机制用观察者模式:发布事件(ApplicationEvent)+ 监听事件(@EventListener),实现解耦。
深度解析
1. 为什么需要事件机制?
用大白话解释:
用户注册成功后,你需要:1) 发送欢迎邮件;2) 发放新用户优惠券;3) 通知运营团队……
如果都写在register()方法里,代码会很臃肿。用事件机制,你只需要发布一个事件,所有监听器会自动执行。
2. 使用示例
1 | |
3. 异步监听(@Async)
1 | |
面试加分回答
「Spring 事件机制用观察者模式,实现解耦。使用步骤:1) 定义事件(继承 ApplicationEvent);2) 发布事件(ApplicationEventPublisher.publishEvent());3) 监听事件(@EventListener)。实际项目中,事件机制常用于:用户注册后发送邮件、订单创建后扣库存、支付成功后发通知等异步场景。另外,@EventListener 可以指定条件(@EventListener(condition = “#event.amount > 100”))。」
Q38:Spring Boot 中如何实现异步调用(@Async)?
一句话结论
用 @EnableAsync 开启异步,用 @Async 标注要异步执行的方法,底层是线程池。
深度解析
1. 为什么需要异步调用?
用大白话解释:
用户注册后,你需要发送欢迎邮件(耗时 3 秒)。如果同步执行,用户要等 3 秒才能看到”注册成功”;如果异步执行,用户立刻看到”注册成功”,邮件在后台慢慢发。
2. 使用示例
1 | |
3. 自定义线程池
1 | |
面试加分回答
「@Async 底层是线程池,默认用 SimpleAsyncTaskExecutor(每次都新建线程,性能差)。实际项目中,一定要自定义线程池(配置核心线程数、最大线程数、队列容量)。另外,@Async 的方法必须是 public,且不能通过自调用(因为底层是 AOP 代理)。如果异步方法返回值是 Future 或 CompletableFuture,可以获取执行结果。」
Q39:Spring Boot 中如何实现定时任务(@Scheduled)?
一句话结论
用 @EnableScheduling 开启定时任务,用 @Scheduled 标注要定时执行的方法。
深度解析
1. 使用示例
1 | |
2. cron 表达式详解
| 字段 | 说明 | 示例 |
|---|---|---|
| 秒 | 0-59 | 0 |
| 分 | 0-59 | 0 |
| 时 | 0-23 | 0 |
| 日 | 1-31 | *(每天) |
| 月 | 1-12 | *(每月) |
| 周 | 1-7(1=周日) | ?(不指定) |
| 年 | 可选 | (留空) |
常用 cron 表达式:
0 0 0 * * ?:每天凌晨 0 点0 0/5 * * * ?:每 5 分钟0 0 9-18 * * MON-FRI:工作日 9 点到 18 点,每小时执行一次
面试加分回答
「@Scheduled 支持 cron 表达式、fixedRate、fixedDelay。cron 表达式功能最强大(可以指定某月某日某时某分某秒)。实际项目中,定时任务一定要配置线程池(默认是单线程,多个任务会互相阻塞)。另外,分布式环境中,定时任务要用分布式调度框架(比如 XXL-Job、Elastic-Job),避免多个实例重复执行。」
Q40:Spring Boot 中如何实现多数据源?
一句话结论
多数据源需要配置多个 DataSource、SqlSessionFactory、TransactionManager,并用 @MapperScan 指定不同数据源扫描哪些 Mapper。
深度解析
1. 为什么需要多数据源?
用大白话解释:
你的项目需要同时操作两个数据库(比如主库 + 从库,或者 MySQL + PostgreSQL),就需要配置多个数据源。
2. 配置示例
1 | |
1 | |
面试加分回答
「多数据源需要配置多个 DataSource、SqlSessionFactory、TransactionManager。实际项目中,多数据源常用于:1) 读写分离(主库写、从库读);2) 微服务拆分时,老系统和新系统用不同数据库。另外,多数据源容易出错(比如事务不生效),推荐用 ShardingSphere 或 MyCAT 管理多数据源。」
七、Spring 面试高频问题速查表
| 问题 | 关键词 |
|---|---|
| IOC 和 DI 的区别 | 思想 vs 实现方式 |
| @Autowired vs @Resource | 按类型 vs 按名称 |
| @Component vs @Bean | 类上 vs 方法上 |
| Bean 作用域 | singleton、prototype、request、session |
| Bean 生命周期 | 实例化 → 属性赋值 → 初始化 → 使用 → 销毁 |
| 循环依赖 | 三级缓存(半成品缓存) |
| AOP 核心概念 | 切面、连接点、切入点、通知 |
| JDK 动态代理 vs CGLIB | 接口 vs 继承 |
| Spring MVC 工作流程 | DispatcherServlet → HandlerMapping → Controller |
| @Controller vs @RestController | 返回视图 vs 返回 JSON |
| Spring Boot 自动配置原理 | @EnableAutoConfiguration + SpringFactoriesLoader |
| @ConfigurationProperties vs @Value | 批量绑定 vs 单个绑定 |
| 事务传播机制 | REQUIRED、REQUIRES_NEW、NESTED |
| @Transactional 失效场景 | 自调用、非 public、异常被 catch |
| Spring Security 核心组件 | SecurityContextHolder、UserDetailsService、PasswordEncoder |
| JWT 集成 | 登录成功生成 JWT、过滤器解析 JWT |
| 服务注册与发现 | Eureka Server、Eureka Client |
| 熔断器 | Hystrix(已停更)、Sentinel(推荐) |
| API 网关 | Spring Cloud Gateway(推荐)、Zuul(已停更) |
| Spring 事件机制 | ApplicationEvent + @EventListener |
| 异步调用 | @EnableAsync + @Async |
| 定时任务 | @EnableScheduling + @Scheduled |
Q41:Spring 中的 @Lazy 是做什么的?有什么用?
一句话结论
@Lazy 让 Bean 延迟初始化(第一次注入时才创建),可以解决循环依赖、加快启动速度。
深度解析
1. 为什么需要 @Lazy?
用大白话解释:
默认情况下,Spring 启动时会立刻创建所有单例 Bean。
如果某个 Bean 很耗时(比如要连接远程服务),会让启动变慢。
@Lazy 让这个 Bean 第一次注入时才创建(而不是启动时创建)。
2. 使用示例
1 | |
3. 解决循环依赖
1 | |
面试加分回答
「@Lazy 让 Bean 延迟初始化,有两个常用场景:1) 解决循环依赖(构造函数注入时);2) 加快启动速度(耗时 Bean 延迟创建)。另外,@Lazy 可以用在 @Bean 方法上,也可以用在 @Autowired 字段上。」
Q42:Spring Boot 中的 @Conditional 是做什么的?
一句话结论
@Conditional 根据条件决定是否创建 Bean,是 Spring Boot 自动配置的灵魂。
深度解析
1. 为什么需要 @Conditional?
用大白话解释:
你引入了一个依赖(比如 spring-boot-starter-data-redis),Spring Boot 会自动配置 RedisTemplate。
但如果你没有引入这个依赖,Spring Boot 就不应该配置 RedisTemplate。
@Conditional 就是用来根据条件决定是否创建 Bean。
2. 常用条件注解
| 条件注解 | 说明 |
|---|---|
@ConditionalOnClass |
类路径有指定类才生效 |
@ConditionalOnMissingBean |
容器中没有指定 Bean 才生效 |
@ConditionalOnBean |
容器中有指定 Bean 才生效 |
@ConditionalOnProperty |
配置文件中指定属性有指定值才生效 |
@ConditionalOnWebApplication |
是 Web 应用才生效 |
3. 使用示例
1 | |
面试加分回答
「@Conditional 是 Spring Boot 自动配置的灵魂。自动配置类(XxxAutoConfiguration)大量使用 @ConditionalOnClass、@ConditionalOnMissingBean 等条件注解,根据类路径、Bean 是否存在、配置属性等条件决定是否生效。如果你想自定义配置,只需要自己定义一个 Bean,因为 @ConditionalOnMissingBean 会让你定义的 Bean 优先。」
Q43:Spring 中的 @Scope 有哪些值?singleton 和 prototype 有什么区别?
一句话结论
singleton(默认)整个容器只有一个实例;prototype 每次注入都创建新实例。
深度解析
1. 六种作用域
| 作用域 | 说明 | 适用场景 |
|---|---|---|
singleton(默认) |
整个 Spring 容器只有一个实例 | Service、Dao(无状态,线程安全) |
prototype |
每次注入/获取都创建新实例 | 有状态的对象(每次都要新的) |
request |
每个 HTTP 请求一个实例 | Web 项目,一次请求内共享 |
session |
每个 HTTP 会话一个实例 | Web 项目,用户登录信息 |
application |
整个 ServletContext 一个实例 | 全局配置 |
websocket |
每个 WebSocket 会话一个实例 | WebSocket 连接 |
2. singleton vs prototype
1 | |
3. 单例 Bean 的线程安全问题
1 | |
解决方案:
- 不用成员变量(无状态设计,推荐)
- 用
ThreadLocal - 用
synchronized(性能差)
面试加分回答
「Spring Bean 默认是单例(singleton),还有 prototype、request、session 等作用域。单例 Bean 不是线程安全的,如果有成员变量会有并发问题。实际项目中,Service 和 Dao 一般都是无状态的(没有成员变量),所以是线程安全的。如果有状态需求,可以用 prototype 作用域。」
Q44:Spring 中的 BeanFactory 和 ApplicationContext 有什么区别?
一句话结论
BeanFactory 是 Spring 的基础设施(延迟加载),ApplicationContext 是 BeanFactory 的超集(启动时加载所有 Bean)。
深度解析
1. BeanFactory(基础版)
1 | |
2. ApplicationContext(增强版,推荐)
1 | |
3. 核心区别对比表
| 对比项 | BeanFactory | ApplicationContext |
|---|---|---|
| 加载方式 | 延迟加载(用的时候才创建) | 启动时加载所有 Bean |
| 功能 | 只有 Bean 管理 | Bean 管理 + 事件发布 + 国际化 + 资源加载 |
| 推荐程度 | ❌ 不推荐 | ✅ 推荐 |
面试加分回答
「BeanFactory 是 Spring 的基础设施,只提供 Bean 管理功能,且是延迟加载(用的时候才创建 Bean)。ApplicationContext 是 BeanFactory 的超集,提供更多企业级功能(事件发布、国际化、资源加载),且启动时加载所有 Bean(方便提前发现错误)。实际项目中,推荐用 ApplicationContext。」
Q45:Spring Boot 中的日志框架是怎么配置的?
一句话结论
Spring Boot 默认用 SLF4J + Logback,配置文件是 application.yml 或 logback-spring.xml。
深度解析
1. 日志框架关系
用大白话解释:
SLF4J 是日志门面(接口),Logback 是日志实现(具体干活的类)。
就像 JDBC 是数据库门面,MySQL 驱动是具体实现。
2. 配置方式
1 | |
1 | |
面试加分回答
「Spring Boot 默认用 SLF4J + Logback。简单配置用 application.yml,高级配置用 logback-spring.xml(支持 Spring 占位符)。实际项目中,推荐用 logback-spring.xml,因为它支持日志分文件(比如按日期轮转)、异步日志(提升性能)。另外,Lombok 的 @Slf4j 注解可以自动生成 log 变量,不用手动写
private static final Logger log = LoggerFactory.getLogger(Xxx.class)。」
Q46:Spring 中的 @PropertySource 是做什么的?
一句话结论
@PropertySource 加载指定的配置文件(.properties 或 .yml),配合 @Value 或 @ConfigurationProperties 使用。
深度解析
1. 为什么需要 @PropertySource?
用大白话解释:
Spring Boot 默认只加载
application.yml或application.properties。
如果你的配置写在别的文件(比如custom.properties),Spring 不知道,需要用 @PropertySource 告诉它。
2. 使用示例
1 | |
1 | |
3. 注意:不支持 YAML 文件
@PropertySource 不支持 YAML 文件(只能加载 .properties 文件)。
如果要加载 YAML 文件,需要用YamlPropertiesFactoryBean。
面试加分回答
「@PropertySource 加载指定的配置文件(.properties),配合 @Value 或 @ConfigurationProperties 使用。但是,@PropertySource 不支持 YAML 文件(只能加载 .properties)。如果要加载 YAML 文件,需要用 YamlPropertiesFactoryBean。另外,Spring Boot 的 @ConfigurationProperties 默认支持 YAML,不需要 @PropertySource。」
Q47:Spring Boot 中的监控端点(Actuator)如何自定义?
一句话结论
自定义监控端点需要实现 HealthIndicator(健康检查)或 Endpoint(自定义端点)。
深度解析
1. 自定义健康检查
1 | |
2. 自定义端点(Endpoint)
1 | |
面试加分回答
「自定义监控端点有两种方式:1) 实现 HealthIndicator(自定义健康检查);2) 用 @Endpoint 注解(自定义端点)。另外,Actuator 可以和 Prometheus + Grafana 集成,实现可视化监控。生产环境中,一定要配置访问权限(不能让所有人都能访问 /actuator/heapdump)。」
Q48:Spring 中的 WebDataBinder 是做什么的?
一句话结论
WebDataBinder 是 Spring MVC 的数据绑定器,负责把 HTTP 请求参数绑定到 Java 对象上。
深度解析
1. 为什么需要 WebDataBinder?
用大白话解释:
前端传来的参数都是字符串(比如
?age=25,age 是字符串"25")。
但 Controller 方法的参数是 int 类型(需要是整数25)。
WebDataBinder 就是负责把字符串参数转换为 Java 对象。
2. 使用示例
1 | |
面试加分回答
「WebDataBinder 是 Spring MVC 的数据绑定器,负责把 HTTP 请求参数绑定到 Java 对象上。实际项目中,@InitBinder 常用于:1) 注册自定义编辑器(比如字符串 → 日期);2) 设置允许/不允许绑定的字段(防止恶意绑定,比如用户恶意绑定
id字段)。另外,Spring 的 @Valid 注解就是配合 WebDataBinder 实现参数校验的。」
Q49:Spring Boot 如何自定义 Banner?
一句话结论
自定义 Banner 只需要把 banner.txt 放到 classpath 根目录,或者用 online Banner 生成器生成艺术字。
深度解析
1. 自定义 Banner(banner.txt)
1 | |
2. 关闭 Banner
1 | |
3. 在线 Banner 生成器
面试加分回答
「自定义 Banner 只需要把 banner.txt 放到 classpath 根目录。Banner 中可以用的变量:${spring-boot.version}(Spring Boot 版本)、${application.title}(应用名称)。另外,也可以关闭 Banner(spring.main.banner-mode=off)。虽然这是个小功能,但面试中经常用来判断候选人是否真的用过 Spring Boot。」
Q50:Spring 面试中最常见的坑有哪些?
一句话结论
Spring 面试中最常见的坑:自调用导致 AOP 失效、@Transactional 失效、Bean 不是线程安全的、循环依赖。
深度解析
1. 自调用导致 AOP 失效
1 | |
解决方式:
- 用 AspectJ 替代 Spring AOP
- 把方法放到另一个 Service
- 用 AopContext.currentProxy() 获取当前代理对象
2. @Transactional 失效
见 Q25(@Transactional 注解失效的常见场景有哪些?)
3. Bean 不是线程安全的
见 Q43(@Scope 有哪些值?singleton 和 prototype 有什么区别?)
4. 循环依赖
见 Q8(什么是循环依赖?怎么解决?)
面试加分回答
「Spring 面试中最常见的坑:1) 自调用导致 AOP 失效(@Transactional、@Cacheable 等注解失效);2) @Transactional 失效(自调用、非 public 方法、异常被 catch、数据库引擎不支持事务);3) 单例 Bean 不是线程安全的(如果有成员变量会有并发问题);4) 循环依赖(虽然 Spring 用三级缓存解决了,但最好重构代码打破循环依赖)。这些坑实际项目中经常遇到,一定要掌握。」
Spring 面试总结:
本文从 Spring Core、Spring MVC、Spring Boot、Spring Security、Spring Cloud 五个方面,全面覆盖了 Spring 面试的核心考点(50 道题)。认真看完本文,Spring 面试不再慌!建议你按以下顺序复习:
- IOC + DI(控制反转、依赖注入、@Autowired vs @Resource)
- Bean 生命周期 + 循环依赖(三级缓存,面试超高频)
- AOP(动态代理、通知类型、切面顺序)
- Spring MVC(DispatcherServlet、工作流程)
- Spring Boot 自动配置原理(@SpringBootApplication、条件注解)
- 事务管理(传播机制、隔离级别、@Transactional 失效场景)
- Spring Security(认证流程、JWT 集成)
- Spring Cloud(服务注册与发现、熔断器、API 网关)
祝你面试顺利!🚀
Q36:Spring 中的 @Lazy 注解有什么作用?
一句话总结:@Lazy 用于延迟初始化 Bean,只有在第一次被使用时才会创建 Bean 实例,解决循环依赖和启动速度问题。
深度解析:
Spring 容器启动时,默认会立即初始化所有单例 Bean。但有时我们希望:
- Bean 的创建成本很高(比如需要加载大文件、建立网络连接)
- Bean 可能永远不会被使用(比如某些条件化的 Bean)
- 存在循环依赖问题
@Lazy 可以标注在:
- @Component 类上:延迟初始化这个类
- @Bean 方法上:延迟初始化这个 Bean
- @Autowired 字段/方法上:延迟注入这个依赖
示例:
1 | |
面试加分回答:
- 可以讲讲
@Lazy的实现原理(Spring 会创建一个代理对象替代真实的 Bean,第一次调用时才真正创建) - 可以对比
@Lazy和@DependsOn(@DependsOn 是指定依赖顺序,@Lazy 是延迟初始化) - 可以讲讲实际应用场景(比如 Spring Boot 的自动配置类很多都用了
@Lazy)
Q37:Spring 中的 @DependsOn 注解有什么作用?
一句话总结:@DependsOn 用于指定 Bean 的初始化顺序,确保依赖的 Bean 先被初始化。
深度解析:
默认情况下,Spring 按照类路径扫描顺序或**@Bean 方法的声明顺序来初始化 Bean。但有时我们需要显式指定初始化顺序**。
使用场景:
- Bean A 的初始化依赖于 Bean B(比如 Bean A 需要读取 Bean B 创建的配置文件)
- 避免某些静态变量或外部环境的初始化顺序问题
示例:
1 | |
面试加分回答:
- 可以讲讲
@DependsOn和@Lazy的区别(@DependsOn是控制初始化顺序,@Lazy是延迟初始化) - 可以讲讲实际应用场景(比如数据库迁移脚本需要在 Service 之前执行)
Q38:Spring 中的 @Primary 和 @Qualifier 有什么区别?
一句话总结:@Primary 用于指定首选 Bean,@Qualifier 用于精确指定要注入的 Bean。
深度解析:
当容器中有多个同类型的 Bean 时,Spring 会抛出 NoUniqueBeanDefinitionException。解决方案有:
@Primary:标注在 Bean 上,表示优先级最高
1
2
3
4
5
6
7
8
9
10
11
12@Component
@Primary // 首选 Bean
public class UserServiceA implements UserService { }
@Component
public class UserServiceB implements UserService { }
@Service
public class OrderService {
@Autowired // 会注入 UserServiceA(因为 @Primary)
private UserService userService;
}@Qualifier:标注在注入点,指定具体的 Bean 名称
1
2
3
4
5
6@Service
public class OrderService {
@Autowired
@Qualifier("userServiceB") // 精确指定
private UserService userService;
}
区别:
@Primary:宽泛的优先级,影响所有注入点@Qualifier:精确的指定,只影响当前注入点
面试加分回答:
- 可以讲讲
@Qualifier的自定义用法(可以创建自定义注解,比如@DevUserService、@ProdUserService) - 可以讲讲实际应用场景(比如多数据源配置,主库用
@Primary,从库用@Qualifier)
Q39:Spring Boot 中的 @Conditional 条件注解有哪些?
一句话总结:@Conditional 系列注解用于根据条件决定是否创建 Bean,是 Spring Boot 自动配置的核心。
深度解析:
Spring Boot 提供了很多 @Conditional 派生注解:
| 注解 | 作用 |
|---|---|
@ConditionalOnClass |
类路径下有指定类时生效 |
@ConditionalOnMissingClass |
类路径下没有指定类时生效 |
@ConditionalOnBean |
容器中有指定 Bean 时生效 |
@ConditionalOnMissingBean |
容器中没有指定 Bean 时生效 |
@ConditionalOnProperty |
配置文件中指定属性有指定值时生效 |
@ConditionalOnExpression |
SpEL 表达式为 true 时生效 |
@ConditionalOnJava |
指定 Java 版本时生效 |
@ConditionalOnWebApplication |
是 Web 应用时生效 |
@ConditionalOnNotWebApplication |
不是 Web 应用时生效 |
示例:
1 | |
面试加分回答:
- 可以讲讲如何自定义
@Conditional注解(实现Condition接口) - 可以讲讲 Spring Boot 自动配置的加载原理(
META-INF/spring.factories或META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports) - 可以讲讲实际应用场景(比如根据配置文件切换不同的实现类)
Q40:Spring 中的 BeanPostProcessor 和 BeanFactoryPostProcessor 有什么区别?
一句话总结:BeanPostProcessor 在 Bean 初始化前后执行,BeanFactoryPostProcessor 在 Bean 定义加载完成后、Bean 实例化前执行。
深度解析:
Spring 容器启动流程:
- 加载 Bean 定义(
BeanDefinition) - 执行
BeanFactoryPostProcessor(可以修改 Bean 定义) - 实例化 Bean
- 执行依赖注入(
@Autowired) - 执行
BeanPostProcessor#postProcessBeforeInitialization - 执行初始化方法(
@PostConstruct、InitializingBean) - 执行
BeanPostProcessor#postProcessAfterInitialization
BeanFactoryPostProcessor 示例:
1 | |
BeanPostProcessor 示例:
1 | |
面试加分回答:
- 可以讲讲
BeanPostProcessor的实际应用(Spring AOP 就是通过BeanPostProcessor创建代理对象的) - 可以讲讲
BeanFactoryPostProcessor的实际应用(Spring Boot 的PropertySourcesPlaceholderConfigurer就是用来处理${...}占位符的) - 可以画一下 Spring 容器启动流程图
Q41:Spring AOP 中的通知(Advice)有哪些类型?
一句话总结:Spring AOP 支持 5 种通知类型:@Before、@AfterReturning、@AfterThrowing、@After、@Around。
深度解析:
| 通知类型 | 注解 | 执行时机 |
|---|---|---|
| 前置通知 | @Before |
目标方法执行前 |
| 返回通知 | @AfterReturning |
目标方法正常返回后 |
| 异常通知 | @AfterThrowing |
目标方法抛出异常后 |
| 后置通知 | @After |
目标方法执行后(无论是否异常) |
| 环绕通知 | @Around |
完全控制目标方法执行 |
示例:
1 | |
面试加分回答:
- 可以讲讲
@Around的强大之处(可以控制是否执行目标方法、可以修改参数和返回值) - 可以讲讲通知的执行顺序(
@Around前部分 →@Before→ 目标方法 →@Around后部分 →@After→@AfterReturning/@AfterThrowing) - 可以讲讲实际应用场景(日志、事务、权限校验、接口耗时统计)
Q42:Spring 中的事务传播行为有哪些?
一句话总结:Spring 定义了 7 种事务传播行为,决定了事务方法嵌套调用时事务如何传播。
深度解析:
| 传播行为 | 说明 |
|---|---|
REQUIRED(默认) |
有事务就加入,没有就新建 |
REQUIRES_NEW |
挂起当前事务,新建一个事务 |
SUPPORTS |
有事务就加入,没有就以非事务方式执行 |
NOT_SUPPORTED |
以非事务方式执行,挂起当前事务 |
MANDATORY |
必须有事务,否则抛异常 |
NEVER |
必须没有事务,否则抛异常 |
NESTED |
如果当前有事务,就在嵌套事务中执行 |
示例:
1 | |
面试加分回答:
- 可以讲讲
REQUIRES_NEW的实际应用场景(比如日志记录,无论主事务是否成功,日志都要记录) - 可以讲讲
NESTED和REQUIRES_NEW的区别(NESTED是嵌套事务,回滚到保存点;REQUIRES_NEW是完全独立的事务) - 可以讲讲事务传播行为的实现原理(Spring 通过
TransactionInterceptor拦截方法调用,根据传播行为决定是否创建新事务)
Q43:Spring 中的 @Transactional 注解有哪些注意事项?
一句话总结:@Transactional 注解有很多坑,比如非public方法不生效、自调用不生效、异常类型不对不回滚等。
深度解析:
常见坑:
只有public方法才生效
1
2
3
4
5
6
7@Service
public class UserService {
@Transactional
private void updateUser() { // ❌ private 方法,事务不生效!
userMapper.update(user);
}
}自调用问题
1
2
3
4
5
6
7
8
9
10
11@Service
public class UserService {
public void updateUser() {
this.updateUserInternal(); // ❌ 自调用,事务不生效!
}
@Transactional
public void updateUserInternal() {
userMapper.update(user);
}
}原因:Spring AOP 是通过代理对象实现的,自调用绕过了代理对象。
异常类型不对不回滚
1
2
3
4
5
6
7
8
9@Transactional
public void updateUser() {
try {
userMapper.update(user);
} catch (Exception e) {
// ❌ 默认只回滚 RuntimeException 和 Error
// 如果 catch 了异常,也不会回滚
}
}解决:
@Transactional(rollbackFor = Exception.class)数据库引擎不支持事务
- MySQL 的 MyISAM 引擎不支持事务,要用 InnoDB。
面试加分回答:
- 可以讲讲如何避免自调用问题(注入自己、使用
AopContext.currentProxy()) - 可以讲讲
@Transactional的实现原理(Spring AOP + 事务管理器) - 可以讲讲实际遇到的坑(比如联调时发现事务没回滚,查了半天才发现是 catch 了异常)
Q44:Spring Boot 中的 Actuator 端点有哪些?如何自定义?
一句话总结:Actuator 提供了很多生产级监控端点(比如 /health、/metrics、/env),可以自定义端点来暴露业务指标。
深度解析:
常用端点:
| 端点 | 说明 |
|---|---|
/actuator/health |
应用健康状态 |
/actuator/metrics |
应用指标(JVM、CPU、内存等) |
/actuator/env |
环境变量和配置属性 |
/actuator/beans |
容器中所有 Bean |
/actuator/mappings |
所有 @RequestMapping 路径 |
/actuator/threaddump |
线程快照 |
/actuator/heapdump |
堆转储 |
自定义端点:
1 | |
面试加分回答:
- 可以讲讲如何自定义健康检查(实现
HealthIndicator接口) - 可以讲讲如何自定义指标(使用
MeterRegistry) - 可以讲讲实际应用场景(比如监控业务指标:订单量、支付成功率等)
Q45:Spring Security 中的 CSRF 防护原理是什么?
一句话总结:CSRF(跨站请求伪造)防护通过在表单中嵌入随机 Token 来验证请求是否来自合法页面。
深度解析:
CSRF 攻击流程:
- 用户登录了
good-site.com(合法网站) - 用户访问了
evil-site.com(恶意网站) - 恶意网站自动提交一个表单到
good-site.com(比如转账请求) - 浏览器会自动带上
good-site.com的 Cookie good-site.com以为是用户自己发起的请求,执行了转账
Spring Security 的 CSRF 防护:
- 服务器生成一个随机 Token,存储在 HttpSession 或 Cookie 中
- 在表单中嵌入一个隐藏字段
_csrf - 提交表单时,服务器验证 Token 是否正确
示例:
1 | |
面试加分回答:
- 可以讲讲为什么 REST API 通常不需要 CSRF 防护(因为 REST API 使用 Token 认证,而不是 Cookie)
- 可以讲讲 CSRF Token 的存储方式(HttpSession、Cookie、Double Submit Cookie)
- 可以讲讲实际应用场景(比如银行系统、支付系统必须开启 CSRF 防护)
Q46:Spring Cloud Gateway 的过滤器有哪些类型?
一句话总结:Spring Cloud Gateway 提供了全局过滤器和路由过滤器,可以在请求转发前后执行逻辑(比如鉴权、限流、日志)。
深度解析:
过滤器类型:
全局过滤器(GlobalFilter):作用于所有路由
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18@Component
public class AuthFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 鉴权逻辑
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
if (token == null || !isValid(token)) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange); // 放行
}
@Override
public int getOrder() {
return -100; // 优先级,数字越小优先级越高
}
}路由过滤器(GatewayFilter):作用于指定路由
1
2
3
4
5
6
7
8
9
10
11spring:
cloud:
gateway:
routes:
- id: user-service
uri: http://localhost:8080
predicates:
- Path=/user/**
filters:
- AddRequestHeader=X-Request-Id, 12345
- AddResponseHeader=X-Response-Time, 100ms
内置过滤器:
AddRequestHeader:添加请求头AddResponseHeader:添加响应头RewritePath:重写路径RequestRateLimiter:限流CircuitBreaker:熔断
面试加分回答:
- 可以讲讲如何自定义全局过滤器(鉴权、日志、限流)
- 可以讲讲过滤器的执行顺序(
Ordered接口或@Order注解) - 可以讲讲实际应用场景(比如统一鉴权、统一日志记录、灰度发布)
Q47:Spring Cloud 中的服务发现原理是什么?
一句话总结:服务发现通过服务注册表来管理服务实例的地址,客户端通过服务名而不是IP地址来调用服务。
深度解析:
服务发现流程:
- 服务注册:服务启动时,向注册中心注册自己的地址(IP、端口、服务名)
- 服务订阅:客户端从注册中心获取服务实例列表
- 健康检查:注册中心定期检测服务实例是否健康
- 服务下线:服务关闭时,向注册中心注销自己
常见注册中心:
| 注册中心 | 一致性协议 | 健康检查 | 适用场景 |
|---|---|---|---|
| Eureka | AP(高可用) | 客户端心跳 | Spring Cloud Netflix |
| Nacos | AP/CP(可切换) | 客户端心跳 + 服务端探测 | Spring Cloud Alibaba |
| Consul | CP(强一致) | 服务端探测 | 多数据中心 |
| Zookeeper | CP(强一致) | 会话机制 | Dubbo |
示例(Eureka):
1 | |
面试加分回答:
- 可以讲讲 CAP 理论(一致性、可用性、分区容错性,三者不可兼得)
- 可以讲讲 Eureka 的自我保护机制(网络分区时,不剔除不健康实例)
- 可以讲讲实际选型建议(内网用 Nacos,跨机房用 Consul)
Q48:Spring Cloud 中的负载均衡原理是什么?
一句话总结:负载均衡通过负载均衡算法从服务实例列表中选择一个实例,常见的有轮询、随机、加权轮询等。
深度解析:
负载均衡类型:
服务端负载均衡(Nginx、F5)
- 客户端不知道有多个服务实例
- 负载均衡由反向代理服务器完成
客户端负载均衡(Ribbon、LoadBalancer)
- 客户端从注册中心获取服务实例列表
- 负载均衡由客户端完成
Ribbon 负载均衡算法:
- 轮询(RoundRobin)
- 随机(Random)
- 重试(Retry)
- 加权响应时间(WeightedResponseTime)
Spring Cloud LoadBalancer(推荐):
1 | |
面试加分回答:
- 可以讲讲 Ribbon 和 LoadBalancer 的区别(Ribbon 已进入维护模式,推荐使用 LoadBalancer)
- 可以讲讲如何自定义负载均衡算法(比如根据机房就近选择)
- 可以讲讲实际应用场景(比如灰度发布,根据版本号路由)
Q49:Spring Cloud 中的熔断原理是什么?
一句话总结:熔断通过监控服务调用失败率,当失败率达到阈值时自动切断调用,避免雪崩效应。
深度解析:
熔断器状态:
- Closed(关闭):正常状态,请求可以访问服务
- Open(打开):失败率达到阈值,请求直接失败(不走网络)
- Half-Open(半开):经过一段时间,放行部分请求,如果成功则关闭熔断器
Hystrix 示例(已进入维护模式):
1 | |
Resilience4j 示例(推荐):
1 | |
面试加分回答:
- 可以讲讲 Hystrix 和 Resilience4j 的区别(Hystrix 已进入维护模式,推荐使用 Resilience4j)
- 可以讲讲熔断和降级的区别(熔断是自动切断调用,降级是返回兜底结果)
- 可以讲讲实际应用场景(比如调用第三方接口时,一定要加熔断)
Q50:Spring Cloud 中的配置中心原理是什么?
一句话总结:配置中心将配置文件集中管理,支持动态刷新,避免修改配置后重启服务。
深度解析:
配置中心流程:
- 配置中心服务端存储配置文件(Git、数据库、本地文件)
- 客户端启动时,从配置中心拉取配置
- 配置中心配置变更时,客户端收到通知并刷新配置
Spring Cloud Config 示例:
1 | |
动态刷新:
1 | |
面试加分回答:
- 可以讲讲 Spring Cloud Config 和 Nacos 的区别(Config 依赖 Git,Nacos 自带配置管理界面)
- 可以讲讲配置加密(敏感配置应该加密存储,比如数据库密码)
- 可以讲讲实际应用场景(比如多环境配置、灰度发布配置)
📚 学习路径总结
恭喜你!如果你认真学完了上面的所有内容,那么你已经掌握了 Spring 的核心知识。下面是一些学习建议,帮助你进一步深入学习。
1. 夯实基础(1-2 周)
- 深入理解 IOC 和 DI
- 理解 Bean 的作用域和生命周期
- 理解 Spring AOP 的原理
2. 动手实践(2-3 周)
- 写一个 Spring MVC 项目(不用 Spring Boot)
- 自己实现一个简单的 IOC 容器
- 自己实现一个简单的 AOP 框架
3. 阅读源码(3-4 周)
- 阅读
ClassPathXmlApplicationContext源码 - 阅读
AutowiredAnnotationBeanPostProcessor源码 - 阅读
Transactional源码
4. 学习 Spring Boot(2-3 周)
- 理解自动配置原理
- 理解起步依赖原理
- 自己写一个 Starter
5. 学习 Spring Cloud(3-4 周)
- 理解服务注册与发现
- 理解负载均衡
- 理解服务熔断
🎓 进阶学习
如果你已经掌握了 Spring 的基础知识,那么可以学习以下内容:
1. Spring 源码深度解析
- 《Spring 源码深度解析》- 郝佳
- 《Spring 技术内幕》- 计文柯
2. Spring Boot 深度解析
- 《Spring Boot 编程思想》- 小马哥
- 《Spring Boot 实战》- Craig Walls
3. Spring Cloud 微服务
- 《Spring Cloud 微服务实战》- 翟永超
- 《深入理解 Spring Cloud 与微服务构建》- 方志朋
💪 最后的建议
- 不要急于求成,要打好基础
- 多写代码,光看不练是没用的
- 看源码,理解 Spring 的设计思想
- 做项目,在实际项目中应用 Spring
- 教别人,教是最好的学
祝你学习顺利!🎉