Spring 面试八股文 50 道|深度详解版(傻子都能看懂)

📖 学习指南

🎯 学习目标:通过本文,你将系统掌握 Spring 的核心知识,能够自信地应对任何相关面试问题。

适合人群

  • 🔰 初学者:想系统学习 Spring 的开发者
  • 🚀 有经验者:想深入理解 Spring 原理的高级开发者
  • 💼 面试准备者:想刷 Spring 面试题的求职者

学习建议

  1. 先理解概念,再深入原理:先搞懂”是什么”,再搞懂”为什么”
  2. 结合实战场景:不要死记硬背,要理解实际应用场景
  3. 动手实践:在自己电脑上安装环境,执行本文的示例代码
  4. 反复复习:面试前一周,每天复习 10 个问题

学习时间估算

  • ⏱️ 快速复习(只看一句话总结):2 小时
  • 📚 系统学习(看深度解析):1-2 天
  • 💪 深入理解(研究源码):1 周+

🗺️ 知识图谱

mindmap
  root((Spring))
    基础概念
      核心概念1
      核心概念2
      核心概念3
    高级特性
      特性1
      特性2
    实战应用
      应用场景1
      应用场景2
    性能优化
      优化技巧1
      优化技巧2

⚠️ 常见陷阱与误区

陷阱 1:概念理解错误

错误示例

1
# 错误的理解或用法

正确做法

  • 正确理解概念
  • 避免常见误区

陷阱 2:忽略边界条件

错误做法

  • 不考虑特殊情况
  • 忽略异常处理

正确做法

  • 总是考虑边界条件
  • 添加异常处理

💡 面试技巧

技巧 1:结构化回答

不要只回答”是什么”,要按照以下结构回答:

  1. 一句话总结(概念)
  2. 深度解析(原理、实现、优缺点)
  3. 面试加分回答(实际项目经验、源码理解、行业最佳实践)

技巧 2:结合实战场景

不要只背概念,要结合实际项目经验回答。

技巧 3:引导到你会的方向

如果遇到不会的问题,不要慌,可以引导到你会的方向。


🎯 实战演练(真实面试场景)

场景 1:请你设计一个系统?

回答思路

  1. 需求分析:明确系统需求
  2. 技术选型:选择合适的技术栈
  3. 架构设计:设计系统架构
  4. 性能优化:考虑性能瓶颈和优化方案

🚀 学习路径总结

第一阶段:基础概念(1-2 天)

  • 理解核心概念
  • 掌握基本操作
  • 完成入门教程

第二阶段:高级特性(2-3 天)

  • 掌握高级特性
  • 理解实现原理
  • 完成进阶教程

第三阶段:实战应用(1 周+)

  • 搭建实际项目
  • 解决实战问题
  • 阅读源码(可选)

第四阶段:面试准备(1 周)

  • 刷完本文的所有问题
  • 复习相关知识点
  • 准备项目经验
  • 模拟面试

📚 扩展学习资源

官方资源

书籍推荐

  • 《Spring 实战》
  • 《Spring 权威指南》

博客推荐


Spring 面试八股文 - 学习指南

🎯 学习目标:真正理解 Spring 的核心原理,而不仅仅是背答案

📖 适用人群:Java 初学者、准备面试的同学、想深入理解 Spring 的开发者

预计学习时间:2-3 天(每天 2-3 小时)

🏆 学习成果:能够自信地回答任何 Spring 面试问题,并理解背后的原理


📚 学习路线(从易到难)

第一阶段:Spring 基础(Day 1)

  1. IOC(控制反转)和 DI(依赖注入) - 理解 Spring 的核心思想
  2. Bean 的作用域和生命周期 - 理解 Spring 如何管理对象
  3. @Component、@Service、@Repository、@Controller - 理解 Bean 的注册
  4. @Autowired 和 @Resource - 理解依赖注入的两种方式

第二阶段:Spring AOP(Day 2 - 上午)

  1. AOP 的基本概念 - 理解切面、切点、通知
  2. JDK 动态代理和 CGLIB 代理 - 理解 Spring AOP 的实现原理
  3. @Before、@After、@Around - 理解各种通知类型
  4. AOP 的实际应用 - 日志、事务、权限校验

第三阶段:Spring MVC(Day 2 - 下午)

  1. Spring MVC 的工作流程 - 理解请求如何到达 Controller
  2. @RequestParam、@PathVariable、@RequestBody - 理解参数绑定
  3. 拦截器和过滤器 - 理解请求拦截机制
  4. 统一异常处理 - 理解 @ControllerAdvice

第四阶段:Spring Boot(Day 3 - 上午)

  1. Spring Boot 的自动配置原理 - 理解 @EnableAutoConfiguration
  2. Spring Boot 的启动流程 - 理解 SpringApplication.run()
  3. 配置文件和属性注入 - 理解 @ConfigurationProperties 和 @Value
  4. Spring Boot Starter - 理解自定义 Starter

第五阶段:Spring Cloud(Day 3 - 下午)

  1. 服务注册与发现 - 理解 Eureka、Nacos
  2. 负载均衡 - 理解 Ribbon、LoadBalancer
  3. 声明式 HTTP 客户端 - 理解 Feign
  4. 服务熔断 - 理解 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:回答问题时,先讲”是什么”,再讲”为什么”

  • 不好的回答:直接讲底层原理,面试官听不懂
  • 好的回答
    1. 先讲”是什么”(一句话总结)
    2. 再讲”为什么”(深度解析)
    3. 最后讲”实际应用场景”(面试加分回答)

技巧 2:用”比喻”帮助理解

  • IOC:就像”不用自己 new 对象,而是找 Spring 要对象”
  • AOP:就像”在不修改源码的情况下,给方法加功能”
  • 动态代理:就像”中介代替房东出租房子”

技巧 3:结合”实际项目经验”回答

  • 不要只背概念,要讲你在实际项目中怎么用的
  • 比如:”我在 XX 项目中,用 Spring AOP 实现了日志记录…”

📖 推荐学习资源

官方文档(最权威)

书籍(深入理解)

  • 《Spring 实战(第 5 版)》- 适合入门
  • 《Spring 技术内幕》- 适合深入理解源码
  • 《Spring Boot 编程思想》- 适合理解设计思想

视频教程(直观易懂)

  • 尚硅谷 Spring 全套教程
  • 黑马程序员 Spring 教程
  • 动力节点 Spring 教程

💪 学习建议

  1. 不要死记硬背,要理解原理
  2. 多写代码,亲自体验 Spring 的各种特性
  3. 看源码,理解 Spring 的设计思想
  4. 做项目,在实际项目中应用 Spring
  5. 刷面试题,熟悉常见的面试问题

现在,让我们开始学习 Spring 吧!🚀


Spring 面试八股文 50 道|深度详解版

写给准备面试的你:
这篇文章不讲废话,每个知识点都从「是什么 → 为什么 → 怎么用」三个层次讲透。
配有大量比喻和场景化解释,目标是让没有 Spring 基础的人也能看懂。
建议配合实际代码练习,理解效果翻倍。


一、Spring Core 基础(1-10)


Q1:什么是 Spring?它的核心思想是什么?

一句话结论

Spring 是一个轻量级的 Java 开发框架,核心思想是 IOC(控制反转)和 AOP(面向切面编程)。


💻 代码示例:理解 IOC

传统方式(没有 IOC)

1
2
3
4
5
6
7
8
9
// 传统方式:自己创建依赖对象
public class UserService {
// 自己 new 对象,耦合度高
private UserDao userDao = new UserDaoImpl();

public void process() {
userDao.save();
}
}

IOC 方式(Spring)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// IOC 方式:由 Spring 创建和注入依赖对象
@Service
public class UserService {
// 不需要自己 new,Spring 会注入
@Autowired
private UserDao userDao;

public void process() {
userDao.save();
}
}

@Repository
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("保存用户");
}
}

对比

方式 优点 缺点
传统方式 简单直接 耦合度高,难以测试
IOC 方式 解耦合,易于测试 需要学习 Spring

⚠️ 常见错误

错误 1:认为 IOC 就是 DI

1
2
❌ 错误理解:IOC 和 DI 是同一个东西
✅ 正确理解:IOC(控制反转)是思想,DI(依赖注入)是实现方式

错误 2:不知道 IOC 的好处

1
2
❌ 错误回答:IOC 就是不用 new 对象
✅ 正确回答:IOC 实现了**解耦合****易于测试****集中管理**

🎯 面试场景模拟

面试官:”请讲讲什么是 Spring IOC?”

你的回答

  1. 一句话总结:IOC(控制反转)就是将对象的创建和依赖注入交给 Spring 容器管理,而不是自己 new 对象。
  2. 举个例子:就像”不用自己去超市买菜,而是点外卖,让外卖小哥送过来”。
  3. 好处:解耦合、易于测试、集中管理。
  4. 实际项目中的应用:我在 XX 项目中,用 Spring IOC 管理了所有的 Service 和 Dao,避免了硬编码依赖。

深度解析

1. Spring 是什么?

用大白话解释:

Spring 就像一个智能工厂,你只需要告诉它你要什么(定义 Bean),它就会帮你创建对象、管理对象、组装对象,你不用自己 new。

传统 Java 开发 vs Spring 开发:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 传统 Java 开发:自己 new,自己管理依赖
public class UserService {
private UserDao userDao = new UserDaoImpl(); // 硬编码,难改

public void process() {
userDao.save();
}
}

// Spring 开发:交给 Spring 管理
@Service
public class UserService {
@Autowired
private UserDao userDao; // Spring 自动注入,灵活

public void process() {
userDao.save();
}
}

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
2
3
4
5
6
7
8
9
@Service
public class UserService {
private final UserDao userDao;

// 构造函数注入(推荐,可以保证依赖不可变)
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}

Setter 方法注入

1
2
3
4
5
6
7
8
9
10
@Service
public class UserService {
private UserDao userDao;

// Setter 方法注入(可选依赖)
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}

字段注入(不推荐):

1
2
3
4
5
6
@Service
public class UserService {
// 字段注入(不推荐,不利于测试和不变性)
@Autowired
private UserDao userDao;
}

为什么构造函数注入是最好的选择?

  1. 不可变性:依赖可以是 final
  2. 可测试性:可以方便地传入 mock 对象
  3. 避免 NPE:依赖在创建对象时就确定了

⚠️ 常见错误

错误 1:混淆 IOC 和 DI

1
2
3
4
❌ 错误理解:IOC 和 DI 是同一个东西
✅ 正确理解:
- IOC(控制反转):**思想**(将对象创建权交给 Spring)
- DI(依赖注入):**实现方式**(通过构造函数、Setter 方法、字段注入)

错误 2:不知道 DI 的三种方式

1
2
❌ 错误回答:DI 就是用 @Autowired 注解
✅ 正确回答:DI 有三种方式:构造函数注入、Setter 方法注入、字段注入

🎯 面试场景模拟

面试官:”请讲讲 IOC 和 DI 的区别?”

你的回答

  1. IOC(控制反转):是一种设计思想,将对象的创建和依赖管理从应用程序代码转移到框架(Spring 容器)。
  2. DI(依赖注入):是 IOC 的具体实现方式,有三种:构造函数注入、Setter 方法注入、字段注入。
  3. 比喻:IOC 就像”不用自己做饭,而是点外卖”;DI 就像”外卖小哥把饭送到你手里”。
  4. 实际项目中的应用:我在 XX 项目中,主要使用构造函数注入,因为它可以保证依赖的不可变性,也便于单元测试。

深度解析

1. IOC(控制反转)详解

用大白话解释:

你去餐厅吃饭,传统的做法是你自己去厨房做(自己 new 对象);
IOC 的做法是你点菜,厨房帮你做(Spring 容器帮你创建对象)。

2. DI(依赖注入)详解

DI = Dependency Injection,翻译过来就是依赖注入

用大白话解释:

你是一个 Service 类,你需要一个 Dao 类。传统做法是你自己去 new 一个 Dao;DI 的做法是Spring 帮你把 Dao 注入进来(你只管用,不管它怎么来的)。

3. DI 的三种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 方式 1:构造器注入(推荐,Spring 官方推荐)
@Service
public class UserService {
private final UserDao userDao;

public UserService(UserDao userDao) { // 构造器注入
this.userDao = userDao;
}
}

// 方式 2:Setter 注入
@Service
public class UserService {
private UserDao userDao;

@Autowired
public void setUserDao(UserDao userDao) { // Setter 注入
this.userDao = userDao;
}
}

// 方式 3:字段注入(不推荐,不利于测试和不变性)
@Service
public class UserService {
@Autowired
private UserDao userDao; // 字段注入(不推荐)
}

面试加分回答

「IOC 是思想,DI 是实现方式。DI 有三种实现方式:构造器注入(推荐)、Setter 注入、字段注入(不推荐)。Spring 4.0 之后官方推荐使用构造器注入,因为它能保证依赖不可变(final),更利于测试。」


Q3:@Autowired 和 @Resource 有什么区别?

一句话结论

@Autowired 是 Spring 的注解,默认按类型注入;@Resource 是 JSR-250 规范,默认按名称注入。


深度解析

1. @Autowired(Spring 注解)

1
2
3
4
5
6
@Autowired
private UserDao userDao; // 默认按类型(UserDao.class)注入

@Autowired
@Qualifier("userDaoImpl") // 如果有多个实现,用 @Qualifier 指定名称
private UserDao userDao;

2. @Resource(JSR-250 规范,Java 标准)

1
2
@Resource(name = "userDaoImpl")  // 默认按名称注入,也可以指定 name
private UserDao userDao;

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
2
3
4
@Component  // 告诉 Spring:这个类是个 Bean,请你帮我管理
public class UserService {
// ...
}

Spring 启动时会自动扫描 @Component@Service@Repository@Controller 注解的类,自动注册为 Bean。

3. @Bean(手动声明)

1
2
3
4
5
6
7
8
@Configuration
public class AppConfig {

@Bean // 告诉 Spring:这个方法的返回值是个 Bean,请你帮我管理
public DataSource dataSource() {
return DataSourceBuilder.create().url("jdbc:mysql://localhost:3306/test").build();
}
}

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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Controller  // 表现层(接收 HTTP 请求)
public class UserController {
// ...
}

@Service // 业务层(业务逻辑)
public class UserService {
// ...
}

@Repository // 持久层(数据库操作,还能自动翻译数据库异常)
public class UserDao {
// ...
}

@Component // 通用组件(不属于上面三层的用这个)
public class EmailService {
// ...
}

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
2
3
4
5
6
7
8
9
10
11
@Service
@Scope("singleton") // 默认,可以不写
public class UserService {
// 整个 Spring 容器只有一个实例
}

@Component
@Scope("prototype") // 每次注入都创建新实例
public class PrototypeBean {
// 每次注入都是新的对象
}

3. 单例 Bean 的线程安全问题

单例 Bean 默认不是线程安全的! 如果有成员变量,多个线程修改会有并发问题。

1
2
3
4
5
6
7
8
@Service
public class UserService {
private int count = 0; // ❌ 成员变量,线程不安全

public void increment() {
count++; // 多个线程同时调用会有问题
}
}

解决方案:

  1. 不用成员变量(无状态设计,推荐)
  2. ThreadLocal
  3. synchronized(性能差)

面试加分回答

「Spring Bean 默认是单例(singleton),还有 prototype、request、session 等作用域。单例 Bean 不是线程安全的,如果有成员变量会有并发问题。实际项目中,Service 和 Dao 一般都是无状态的(没有成员变量),所以是线程安全的。如果有状态需求,可以用 prototype 作用域。」


Q7:Spring 中的 Bean 生命周期有哪些阶段?

一句话结论

Bean 生命周期分为 5 个阶段:实例化 → 属性赋值 → 初始化 → 使用 → 销毁。


深度解析

1. Bean 生命周期流程图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
实例化(createBeanInstance)

属性赋值(populateBean)

Aware 接口回调(BeanNameAware、BeanFactoryAware...)

BeanPostProcessor.postProcessBeforeInitialization

初始化(@PostConstruct、InitializingBean、init-method

BeanPostProcessor.postProcessAfterInitialization

Bean 就绪(可以使用了)

销毁(@PreDestroyDisposableBeandestroy-method

2. 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Component
public class MyBean implements BeanNameAware, InitializingBean, DisposableBean {

public MyBean() {
System.out.println("1. 实例化(构造器)");
}

@Override
public void setBeanName(String name) {
System.out.println("2. Aware 接口回调(setBeanName)");
}

@PostConstruct
public void init() {
System.out.println("3. 初始化(@PostConstruct)");
}

@Override
public void afterPropertiesSet() {
System.out.println("4. 初始化(InitializingBean)");
}

public void use() {
System.out.println("5. Bean 使用中...");
}

@PreDestroy
public void destroy() {
System.out.println("6. 销毁(@PreDestroy)");
}

@Override
public void destroy() {
System.out.println("7. 销毁(DisposableBean)");
}
}

3. 面试常问:三级缓存解决循环依赖

Spring 用三级缓存解决循环依赖(A 依赖 B,B 依赖 A):

1
2
3
4
// Spring 三级缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(); // 一级缓存:成品 Bean
private final Map<String, Object> earlySingletonObjects = new HashMap<>(); // 二级缓存:半成品 Bean(已实例化,未初始化)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(); // 三级缓存:Bean 工厂(可以创建代理对象)

面试加分回答

「Bean 生命周期有 5 个阶段:实例化 → 属性赋值 → 初始化 → 使用 → 销毁。其中初始化阶段有很多扩展点(@PostConstruct、InitializingBean、init-method)。另外,Spring 用三级缓存解决循环依赖,这是面试超高频题,需要重点掌握。」


Q8:Spring 中什么是循环依赖?怎么解决?

一句话结论

循环依赖就是 A 依赖 B,B 依赖 A。Spring 用三级缓存解决单例 Bean 的循环依赖,但原型 Bean 无法解决。


深度解析

1. 什么是循环依赖?

1
2
3
4
5
6
7
8
9
10
11
@Service
public class AService {
@Autowired
private BService bService; // A 依赖 B
}

@Service
public class BService {
@Autowired
private AService aService; // B 依赖 A(循环依赖)
}

2. Spring 怎么解决循环依赖?(三级缓存)

用大白话解释:

就像两个人互相给对方做担保:

  1. A 先实例化(创建一个空对象,属性还没赋值),放进”半成品缓存”
  2. A 发现需要 B,去创建 B
  3. B 实例化,发现需要 A,从”半成品缓存”拿到 A 的半成品(虽然属性没赋值完,但可以用)
  4. B 初始化完成,放进”成品缓存”
  5. A 拿到 B 的成品,完成属性赋值,初始化完成,放进”成品缓存”

3. 三级缓存详解

1
2
3
4
// Spring 源码中的三级缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(); // 一级缓存:成品 Bean(完全初始化好的)
private final Map<String, Object> earlySingletonObjects = new HashMap<>(); // 二级缓存:半成品 Bean(已实例化,未初始化)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(); // 三级缓存:Bean 工厂(可以创建代理对象)

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Aspect  // 声明这是一个切面
@Component
public class LogAspect {

@Before("execution(* com.example.service.*.*(..))") // 前置通知:方法执行前执行
public void logBefore(JoinPoint joinPoint) {
System.out.println("方法执行前:" + joinPoint.getSignature().getName());
}

@After("execution(* com.example.service.*.*(..))") // 后置通知:方法执行后执行(不管是否异常)
public void logAfter(JoinPoint joinPoint) {
System.out.println("方法执行后:" + joinPoint.getSignature().getName());
}

@Around("execution(* com.example.service.*.*(..))") // 环绕通知:方法执行前后都能干预(最强大)
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("方法执行前");
Object result = joinPoint.proceed(); // 执行原始方法
System.out.println("方法执行后");
return result;
}
}

面试加分回答

「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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 接口
public interface UserService {
void save();
}

// 实现类
@Service
public class UserServiceImpl implements UserService {
@Override
public void save() {
System.out.println("保存用户");
}
}

// JDK 动态代理
UserService proxy = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
(proxy1, method, args) -> {
System.out.println("前置逻辑");
Object result = method.invoke(userService, args);
System.out.println("后置逻辑");
return result;
}
);

2. CGLIB 代理(没接口时用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 没有接口的类
@Service
public class UserService {
public void save() {
System.out.println("保存用户");
}
}

// CGLIB 代理(通过继承)
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
System.out.println("前置逻辑");
Object result = proxy.invokeSuper(obj, args);
System.out.println("后置逻辑");
return result;
});
UserService proxy = (UserService) enhancer.create();

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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
HTTP 请求

DispatcherServlet(前端控制器,统一接收请求)

HandlerMapping(处理器映射器,找到对应的 Controller)

HandlerAdapter(处理器适配器,执行 Controller 方法)

Controller(执行业务逻辑)

Service(业务逻辑层)

DAO(数据访问层)

返回 ModelAndView(视图 + 数据)

ViewResolver(视图解析器,解析视图)

返回 HTTP 响应(HTML / JSON

2. 核心组件详解

组件 作用
DispatcherServlet 前端控制器,统一接收请求(入口)
HandlerMapping 处理器映射器,根据 URL 找到对应的 Controller
HandlerAdapter 处理器适配器,执行 Controller 方法
Controller 处理器,执行业务逻辑
ViewResolver 视图解析器,解析视图名称(比如返回 “user/list” → /WEB-INF/views/user/list.jsp

3. 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
@RequestMapping("/users")
public class UserController {

@Autowired
private UserService userService;

@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUserById(id); // DispatcherServlet 帮你调用这个方法
}
}

面试加分回答

「Spring MVC 的核心是 DispatcherServlet(前端控制器),所有请求都经过它,然后分发给对应的 Controller。这个流程和餐厅点菜很像:DispatcherServlet 是前台,HandlerMapping 是菜单(找菜品),Controller 是厨师(做菜)。另外,现在主流是前后端分离,Controller 返回 JSON(@ResponseBody 或 @RestController),不再返回视图。」


Q12:@RequestParam、@PathVariable、@RequestBody 有什么区别?

一句话结论

@RequestParam 获取查询参数,@PathVariable 获取路径参数,@RequestBody 获取请求体(JSON)。


深度解析

1. @RequestParam(获取查询参数)

1
2
3
4
5
6
// URL:/users?name=张三&age=25
@GetMapping("/users")
public List<User> getUsers(@RequestParam String name, @RequestParam Integer age) {
// name = "张三",age = 25
return userService.findUsers(name, age);
}

2. @PathVariable(获取路径参数)

1
2
3
4
5
6
// URL:/users/123
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
// id = 123
return userService.getUserById(id);
}

3. @RequestBody(获取请求体,JSON)

1
2
3
4
5
6
// URL:/users,请求体:{"name": "张三", "age": 25}
@PostMapping("/users")
public User createUser(@RequestBody User user) {
// user.name = "张三",user.age = 25
return userService.save(user);
}

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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
HTTP 请求

Filter.doFilter (所有请求,包括静态资源)

DispatcherServlet

Interceptor.preHandle (只拦截 Controller 请求)

Controller 方法执行

Interceptor.postHandle (Controller 执行后,视图渲染前)

Interceptor.afterCompletion (视图渲染后)

Filter.doFilter 返回

HTTP 响应

2. 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Filter(Servlet 规范,web.xml 或 @WebFilter 配置)
@WebFilter("/*")
public class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
System.out.println("Filter:请求进入");
chain.doFilter(request, response); // 放行
System.out.println("Filter:响应返回");
}
}

// Interceptor(Spring MVC 规范,实现 HandlerInterceptor)
@Component
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
System.out.println("Interceptor:进入 Controller 前");
return true; // 返回 true 放行,false 拦截
}
}

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
3
4
5
6
7
8
9
@Controller
public class UserController {

@GetMapping("/users")
public String getUsers(Model model) {
model.addAttribute("users", userService.findAll());
return "user/list"; // 返回视图名称,Spring 会渲染 user/list.html
}
}

2. @RestController(返回 JSON,前后端分离)

1
2
3
4
5
6
7
8
@RestController  // = @Controller + @ResponseBody
public class UserController {

@GetMapping("/users")
public List<User> getUsers() {
return userService.findAll(); // 直接返回 JSON(Spring 自动序列化)
}
}

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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestController
public class UserController {

@GetMapping("/users/{id}")
public Result getUser(@PathVariable Long id) {
try {
User user = userService.getUserById(id);
return Result.success(user);
} catch (UserNotFoundException e) {
return Result.error("用户不存在");
} catch (Exception e) {
return Result.error("系统错误");
}
}
// ❌ 每个方法都要写 try-catch,代码冗余
}

2. 统一异常处理(推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@ControllerAdvice  // 全局异常处理(所有 Controller 都生效)
@ResponseBody
public class GlobalExceptionHandler {

@ExceptionHandler(UserNotFoundException.class) // 处理用户不存在异常
public Result handleUserNotFound(UserNotFoundException e) {
return Result.error(404, e.getMessage());
}

@ExceptionHandler(MethodArgumentNotValidException.class) // 处理参数校验异常
public Result handleValidation(MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getFieldError().getDefaultMessage();
return Result.error(400, msg);
}

@ExceptionHandler(Exception.class) // 处理其他所有异常
public Result handleException(Exception e) {
return Result.error(500, "系统错误:" + e.getMessage());
}
}

3. 使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
public class UserController {

@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
User user = userService.getUserById(id);
// 如果找不到用户,直接抛异常,不用 try-catch
if (user == null) {
throw new UserNotFoundException("用户不存在");
}
return user;
}
}

面试加分回答

「统一异常处理用 @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
2
3
4
5
6
7
8
9
10
11
12
13
14
// 传统 Spring 开发:需要大量 XML 配置
// applicationContext.xml
<bean id="userService" class="com.example.UserService" />
<bean id="userDao" class="com.example.UserDao" />
<!-- 还需要配置数据源、事务管理器、视图解析器... -->

// Spring Boot 开发:几乎零配置
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// 只需要启动类,其他都自动配置!

面试加分回答

「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
3
4
5
6
7
8
9
10
11
12
13
@SpringBootApplication

@EnableAutoConfiguration

AutoConfigurationImportSelector(导入自动配置类)

SpringFactoriesLoader.loadFactoryNames()

读取所有 jar 包中的 META-INF/spring.factories

加载 XxxAutoConfiguration 类

根据条件注解(@ConditionalOnClass 等)决定是否生效

2. 核心注解 @SpringBootApplication

1
2
3
4
5
6
@SpringBootApplication  // = @Configuration + @EnableAutoConfiguration + @ComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

3. 自动配置类示例(RedisAutoConfiguration)

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
@ConditionalOnClass(RedisOperations.class) // 类路径有 RedisOperations 才生效
@EnableConfigurationProperties(RedisProperties.class) // 绑定配置属性
public class RedisAutoConfiguration {

@Bean
@ConditionalOnMissingBean // 容器中没有 RedisTemplate 才生效(用户可以自定义)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
return template;
}
}

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
2
3
4
5
6
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args); // 启动入口
}
}

启动流程(简化版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1. 加载 SpringApplication(推断应用类型、加载 ApplicationContextInitializer、加载 ApplicationListener)

2. 运行 run() 方法

3. 创建 ApplicationContext(根据应用类型创建:Servlet -> AnnotationConfigServletWebServerApplicationContext)

4. 刷新 ApplicationContext(核心步骤)
- 扫描 Bean(@ComponentScan)
- 加载自动配置类(@EnableAutoConfiguration)
- 创建所有 Bean
- 启动内嵌 Tomcat(如果是 Web 应用)

5. 调用 ApplicationRunner 和 CommandLineRunner

6. 启动完成,开始接收请求

2. 核心代码(SpringApplication.run())

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public ConfigurableApplicationContext run(String... args) {
// 1. 启动计时器
StopWatch stopWatch = new StopWatch();
stopWatch.start();

// 2. 创建 ApplicationContext
ConfigurableApplicationContext context = null;
context = createApplicationContext(); // 根据应用类型创建上下文

// 3. 刷新上下文(核心步骤,自动配置、Bean 扫描都在这步)
refreshContext(context);

// 4. 调用 Runner(ApplicationRunner、CommandLineRunner)
callRunners(context, args);

// 5. 启动完成
stopWatch.stop();
return context;
}

面试加分回答

「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
2
3
4
5
6
7
8
9
# application.yml(推荐,结构清晰)
server:
port: 8080

spring:
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: 123456
1
2
3
4
5
6
# application.properties(传统格式)
server.port=8080

spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=123456

2. 配置优先级(从高到低)

优先级 位置 说明
1 命令行参数(--server.port=8081 最高
2 外部配置文件(file:./config/ 项目根目录下的 config 文件夹
3 外部配置文件(file:./ 项目根目录
4 内部配置文件(classpath:/config/ resources/config 文件夹
5 内部配置文件(classpath:/ resources 目录(默认)

3. 多环境配置(Profile)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# application.yml(主配置)
spring:
profiles:
active: dev # 激活 dev 环境

# application-dev.yml(开发环境)
server:
port: 8080

spring:
datasource:
url: jdbc:mysql://localhost:3306/dev_db

# application-prod.yml(生产环境)
server:
port: 80

spring:
datasource:
url: jdbc:mysql://prod-server:3306/prod_db

面试加分回答

「Spring Boot 配置文件优先级:命令行参数 > 外部配置文件(file:./config/)> 外部配置文件(file:./)> 内部配置文件(classpath:/config/)> 内部配置文件(classpath:/)。多环境配置用 spring.profiles.active 指定,配置文件名是 application-{profile}.yml。另外,配置绑定用 @ConfigurationProperties(推荐,类型安全)或 @Value(不推荐,功能弱)。」


Q20:@ConfigurationProperties 和 @Value 有什么区别?

一句话结论

@ConfigurationProperties 批量绑定配置(类型安全,支持校验),@Value 单个绑定配置(功能弱,不支持校验)。


深度解析

1. @ConfigurationProperties(推荐)

1
2
3
4
5
6
7
8
@Data
@ConfigurationProperties(prefix = "app.user") // 绑定 app.user 前缀的配置
@Component
public class UserProperties {
private String name; // 对应 app.user.name
private Integer age; // 对应 app.user.age
private List<String> hobbies; // 对应 app.user.hobbies
}
1
2
3
4
5
6
7
8
# application.yml
app:
user:
name: 张三
age: 25
hobbies:
- 篮球
- 足球

2. @Value(不推荐)

1
2
3
4
5
6
7
8
@Component
public class UserProperties {
@Value("${app.user.name}")
private String name;

@Value("${app.user.age}")
private Integer age; // ❌ 不支持复杂类型(List、Map)
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 1. 创建自动配置类
@Configuration
@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyProperties.class)
public class MyAutoConfiguration {

@Bean
@ConditionalOnMissingBean
public MyService myService(MyProperties properties) {
return new MyService(properties);
}
}

// 2. 创建属性配置类
@ConfigurationProperties(prefix = "my.service")
public class MyProperties {
private String name;
private Integer timeout;
// getter/setter
}

// 3. 创建 META-INF/spring.factories
# resources/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.MyAutoConfiguration

面试加分回答

「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
2
3
4
5
6
7
8
9
# application.yml
management:
endpoints:
web:
exposure:
include: health,info,beans,env,metrics,threaddump # 暴露哪些端点
endpoint:
health:
show-details: always # 显示健康详情

3. 自定义健康检查

1
2
3
4
5
6
7
8
9
10
11
@Component
public class CustomHealthIndicator implements HealthIndicator {
@Override
public Health health() {
if (isThirdPartyServiceUp()) {
return Health.up().withDetail("service", "第三方服务正常").build();
} else {
return Health.down().withDetail("service", "第三方服务不可用").build();
}
}
}

面试加分回答

「Actuator 是 Spring Boot 的监控神器,提供了很多开箱即用的端点。生产环境中,一定要配置访问权限(不能让所有人都能访问 /actuator/heapdump),并且只暴露必要的端点(比如 health、info)。另外,Actuator 可以和 Prometheus + Grafana 集成,实现可视化监控。」


Q23:Spring Boot 中如何实现全局异常处理?

一句话结论

用 @ControllerAdvice + @ExceptionHandler 统一处理异常,不用在每个 Controller 里写 try-catch。


深度解析

1. 统一异常处理示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@ControllerAdvice  // 全局异常处理(所有 Controller 都生效)
@ResponseBody
public class GlobalExceptionHandler {

@ExceptionHandler(UserNotFoundException.class) // 处理用户不存在异常
public Result handleUserNotFound(UserNotFoundException e) {
return Result.error(404, e.getMessage());
}

@ExceptionHandler(MethodArgumentNotValidException.class) // 处理参数校验异常
public Result handleValidation(MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getFieldError().getDefaultMessage();
return Result.error(400, msg);
}

@ExceptionHandler(Exception.class) // 处理其他所有异常
public Result handleException(Exception e) {
return Result.error(500, "系统错误:" + e.getMessage());
}
}

2. 自定义异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 自定义异常
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}

// 使用自定义异常
@RestController
public class UserController {

@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
User user = userService.getUserById(id);
if (user == null) {
throw new UserNotFoundException("用户不存在");
}
return user;
}
}

面试加分回答

「全局异常处理用 @ControllerAdvice + @ExceptionHandler,可以拦截所有 Controller 抛出的异常,返回统一的错误格式。这样不用在每个 Controller 里写 try-catch,代码更简洁。另外,@ControllerAdvice 还可以做全局数据绑定(@InitBinder)和全局数据预处理(@ModelAttribute)。」


Q24:Spring Boot 中的事务管理是怎么实现的?

一句话结论

Spring Boot 事务管理通过 @Transactional 注解实现,底层是 AOP(动态代理)。


深度解析

1. @Transactional 使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
public class UserService {

@Autowired
private UserDao userDao;

@Transactional // 开启事务
public void saveUser(User user) {
userDao.save(user);
// 如果这里抛异常,事务会回滚
if (user.getName().equals("error")) {
throw new RuntimeException("保存失败");
}
}
}

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
3
4
5
6
7
8
9
10
11
12
13
14
@Service
public class UserService {

public void saveUser(User user) {
saveUserInner(user); // ❌ 自调用,事务失效
}

@Transactional
public void saveUserInner(User user) {
userDao.save(user);
throw new RuntimeException("保存失败");
// 事务不会回滚!因为自调用绕过了代理对象
}
}

2. 非 public 方法

1
2
3
4
5
6
7
8
@Service
public class UserService {

@Transactional // ❌ 非 public 方法,事务失效
protected void saveUser(User user) {
userDao.save(user);
}
}

3. 异常被 catch

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class UserService {

@Transactional
public void saveUser(User user) {
try {
userDao.save(user);
throw new RuntimeException("保存失败");
} catch (Exception e) {
// ❌ 异常被 catch,事务不会回滚
}
}
}

4. 数据库引擎不支持事务

1
2
// MySQL MyISAM 引擎不支持事务,@Transactional 不会生效
// 必须用 InnoDB 引擎

面试加分回答

「@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
2
3
4
5
6
7
8
9
10
11
用户登录

UsernamePasswordAuthenticationFilter(拦截登录请求)

AuthenticationManager(认证管理器,调用 AuthenticationProvider

AuthenticationProvider(调用 UserDetailsService 查用户、PasswordEncoder 校验密码)

认证成功 → 把 Authentication 放入 SecurityContextHolder

认证失败 → 返回 401 Unauthorized

3. 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 1. 实现 UserDetailsService(根据用户名查用户)
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

@Autowired
private UserDao userDao;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userDao.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
// 返回 UserDetails(Spring Security 的 User 对象)
return org.springframework.security.core.userdetails.User
.withUsername(user.getUsername())
.password(user.getPassword())
.authorities(user.getRoles().toArray(new String[0]))
.build();
}
}

// 2. 配置 PasswordEncoder(密码加密)
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // 用 BCrypt 加密
}

面试加分回答

「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
3
4
5
6
7
8
9
10
11
12
13
用户登录(/login

登录成功 → 生成 JWT(包含用户名、权限、过期时间)

返回 JWT 给前端

后续请求:前端在 Header 中携带 JWT(Authorization: Bearer xxx)

JWT 过滤器(OncePerRequestFilter)解析 JWT

将认证信息放入 SecurityContextHolder

访问资源

2. 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 1. 登录成功后生成 JWT
@RestController
public class AuthController {

@PostMapping("/login")
public Result login(@RequestBody LoginRequest request) {
// 认证用户
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
);

// 生成 JWT
String jwt = JwtUtils.generateJwt(authentication);

// 返回 JWT
return Result.success(jwt);
}
}

// 2. JWT 过滤器(解析 JWT)
@Component
public class JwtFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 从 Header 中获取 JWT
String jwt = request.getHeader("Authorization");
if (jwt != null && jwt.startsWith("Bearer ")) {
jwt = jwt.substring(7);
// 解析 JWT
Authentication authentication = JwtUtils.parseJwt(jwt);
// 将认证信息放入 SecurityContextHolder
SecurityContextHolder.getContext().setAuthentication(authentication);
}

filterChain.doFilter(request, response);
}
}

面试加分回答

「JWT 集成需要三步:1) 登录成功后生成 JWT;2) 自定义过滤器(OncePerRequestFilter)解析 JWT;3) 将认证信息放入 SecurityContextHolder。JWT 的优点是无状态(不用存 Session),缺点是无法主动失效(只能等过期)。实际项目中,一般会配合 Redis 实现 JWT 黑名单(用户退出登录时,把 JWT 加入黑名单)。」


Q28:Spring Security 中如何自定义登录成功/失败处理?

一句话结论

自定义登录成功/失败处理需要:1) 实现 AuthenticationSuccessHandler/AuthenticationFailureHandler;2) 在 SecurityConfig 中配置。


深度解析

1. 自定义登录成功处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

@Autowired
private ObjectMapper objectMapper;

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
// 登录成功,返回 JSON
response.setContentType("application/json;charset=UTF-8");
Result result = Result.success("登录成功", authentication.getName());
response.getWriter().write(objectMapper.writeValueAsString(result));
}
}

2. 自定义登录失败处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {

@Autowired
private ObjectMapper objectMapper;

@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
// 登录失败,返回 JSON
response.setContentType("application/json;charset=UTF-8");
Result result = Result.error(401, exception.getMessage());
response.getWriter().write(objectMapper.writeValueAsString(result));
}
}

3. 在 SecurityConfig 中配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private CustomAuthenticationSuccessHandler successHandler;

@Autowired
private CustomAuthenticationFailureHandler failureHandler;

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginProcessingUrl("/login") // 登录 URL
.successHandler(successHandler) // 登录成功处理
.failureHandler(failureHandler) // 登录失败处理
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/login", "/register").permitAll() // 放行登录、注册
.anyRequest().authenticated() // 其他请求需要认证
.and()
.csrf().disable(); // 关闭 CSRF(前后端分离不需要)
}
}

面试加分回答

「自定义登录成功/失败处理需要实现 AuthenticationSuccessHandler/AuthenticationFailureHandler,然后在 SecurityConfig 中配置。实际项目中,前后端分离一般用 JSON 返回(而不是跳转页面),所以需要自定义成功/失败处理。另外,Spring Security 默认登录成功会跳转,前后端分离需要返回 JSON。」


Q29:Spring Security 中如何自定义权限校验?

一句话结论

自定义权限校验需要:1) 实现 AccessDecisionManager 或 @PreAuthorize;2) 在方法上用 @PreAuthorize 指定权限要求。


深度解析

1. 方法级权限控制(推荐,用 @PreAuthorize)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RestController
@RequestMapping("/users")
public class UserController {

@PreAuthorize("hasRole('ADMIN')") // 只有 ADMIN 角色才能访问
@GetMapping("/admin")
public Result adminOnly() {
return Result.success("只有管理员才能看到这个");
}

@PreAuthorize("hasAuthority('user:read')") // 只有 user:read 权限才能访问
@GetMapping("/{id}")
public Result getUser(@PathVariable Long id) {
return Result.success(userService.getUserById(id));
}

@PreAuthorize("hasRole('ADMIN') or @securityService.isOwner(#id)") // 复杂表达式
@PutMapping("/{id}")
public Result updateUser(@PathVariable Long id, @RequestBody User user) {
return Result.success(userService.updateUser(user));
}
}

2. 开启方法级权限控制

1
2
3
4
5
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启 @PreAuthorize 和 @PostAuthorize
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
// 配置方法级权限控制
}

3. 自定义权限校验逻辑

1
2
3
4
5
6
7
8
9
10
11
@Service
public class SecurityService {

public boolean isOwner(Long userId) {
// 判断当前用户是否是资源的所有者
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String currentUsername = authentication.getName();
User user = userService.getUserById(userId);
return user.getUsername().equals(currentUsername);
}
}

面试加分回答

「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
2
3
4
5
6
7
8
9
10
11
用户请求

API 网关(Spring Cloud Gateway)

服务注册中心(Eureka/Nacos)

服务 A → 服务 B → 服务 C

配置中心(Spring Cloud Config/Nacos)

熔断、限流(Hystrix/Resilience4j/Sentinel)

面试加分回答

「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
2
3
Eureka Server(注册中心)
↑ 注册、心跳 ↓ 拉取服务列表
Eureka Client(服务 A、服务 B、服务 C)

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
2
3
4
5
6
7
8
9
10
11
12
13
14
@Bean
@LoadBalanced // 开启负载均衡
public RestTemplate restTemplate() {
return new RestTemplate();
}

// 使用(直接用服务名,不用写 IP)
@Autowired
private RestTemplate restTemplate;

public User getUser(Long id) {
// 不用写 IP,直接写服务名(user-service)
return restTemplate.getForObject("http://user-service/users/" + id, User.class);
}

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
3
4
5
6
7
@Autowired
private RestTemplate restTemplate;

public User getUser(Long id) {
// 手动拼接 URL,手动调用
return restTemplate.getForObject("http://user-service/users/" + id, User.class);
}

2. Feign(声明式,推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1. 定义 Feign 接口
@FeignClient(name = "user-service")
public interface UserServiceClient {

@GetMapping("/users/{id}")
User getUser(@PathVariable Long id);
}

// 2. 直接使用(像调用本地方法一样)
@Autowired
private UserServiceClient userServiceClient;

public User getUser(Long id) {
return userServiceClient.getUser(id); // 像调用本地方法
}

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
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class UserService {

@HystrixCommand(fallbackMethod = "getUserFallback") // 熔断 + 降级
public User getUser(Long id) {
return restTemplate.getForObject("http://user-service/users/" + id, User.class);
}

// 降级方法(getUser 失败时会调用这个方法)
public User getUserFallback(Long id) {
return new User(id, "默认用户", 0); // 返回默认值
}
}

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
2
3
4
5
6
7
8
9
10
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service # 转发到 user-service(负载均衡)
predicates:
- Path=/api/users/** # 路径匹配
filters:
- StripPrefix=1 # 去掉前缀(/api/users/1 → /users/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
3
// Spring 用工厂模式创建 Bean
BeanFactory beanFactory = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = beanFactory.getBean(UserService.class); // 工厂创建对象

2. 单例模式(Singleton Pattern)

1
2
3
4
5
// Spring Bean 默认是单例
@Service // 整个 Spring 容器只有一个实例
public class UserService {
// ...
}

3. 代理模式(Proxy Pattern)

1
2
3
// Spring AOP 用代理模式
// JDK 动态代理(有接口时用)
// CGLIB 代理(没接口时用)

4. 模板方法模式(Template Method Pattern)

1
2
3
// Spring 用模板方法模式简化开发
JdbcTemplate jdbcTemplate; // 模板方法模式(固定流程:获取连接、执行 SQL、关闭连接)
jdbcTemplate.query("SELECT * FROM users", (rs, rowNum) -> new User(rs.getLong("id"), rs.getString("name")));

5. 观察者模式(Observer Pattern)

1
2
3
4
5
// Spring 事件机制用观察者模式
@EventListener // 监听事件
public void handleUserRegisterEvent(UserRegisterEvent event) {
// 发送欢迎邮件
}

面试加分回答

「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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 1. 定义事件(继承 ApplicationEvent)
public class UserRegisterEvent extends ApplicationEvent {
private String username;

public UserRegisterEvent(Object source, String username) {
super(source);
this.username = username;
}

public String getUsername() {
return username;
}
}

// 2. 发布事件(在 Service 中)
@Service
public class UserService {

@Autowired
private ApplicationEventPublisher eventPublisher;

public void register(User user) {
// 保存用户
userDao.save(user);

// 发布事件(通知所有监听器)
eventPublisher.publishEvent(new UserRegisterEvent(this, user.getUsername()));
}
}

// 3. 监听事件(可以有很多个监听器)
@EventListener // 监听 UserRegisterEvent
public void sendWelcomeEmail(UserRegisterEvent event) {
// 发送欢迎邮件
emailService.sendWelcomeEmail(event.getUsername());
}

@EventListener
public void sendCoupon(UserRegisterEvent event) {
// 发放新用户优惠券
couponService.sendNewUserCoupon(event.getUsername());
}

3. 异步监听(@Async)

1
2
3
4
5
6
@EventListener
@Async // 异步执行(需要开启 @EnableAsync)
public void sendWelcomeEmail(UserRegisterEvent event) {
// 异步发送欢迎邮件(不阻塞主线程)
emailService.sendWelcomeEmail(event.getUsername());
}

面试加分回答

「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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 1. 开启异步(启动类或配置类)
@SpringBootApplication
@EnableAsync // 开启异步调用
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

// 2. 标注异步方法
@Service
public class EmailService {

@Async // 异步执行(底层是线程池)
public void sendWelcomeEmail(String username) {
// 模拟耗时操作(3 秒)
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("欢迎邮件发送成功:" + username);
}
}

// 3. 调用异步方法
@Autowired
private EmailService emailService;

public void register(User user) {
userDao.save(user);
emailService.sendWelcomeEmail(user.getUsername()); // 异步执行(立刻返回)
System.out.println("注册成功"); // 不等邮件发完,立刻返回
}

3. 自定义线程池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class AsyncConfig {

@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5); // 核心线程数
executor.setMaxPoolSize(10); // 最大线程数
executor.setQueueCapacity(25); // 队列容量
executor.setThreadNamePrefix("Async-"); // 线程名前缀
executor.initialize();
return executor;
}
}

面试加分回答

「@Async 底层是线程池,默认用 SimpleAsyncTaskExecutor(每次都新建线程,性能差)。实际项目中,一定要自定义线程池(配置核心线程数、最大线程数、队列容量)。另外,@Async 的方法必须是 public,且不能通过自调用(因为底层是 AOP 代理)。如果异步方法返回值是 Future 或 CompletableFuture,可以获取执行结果。」


Q39:Spring Boot 中如何实现定时任务(@Scheduled)?

一句话结论

用 @EnableScheduling 开启定时任务,用 @Scheduled 标注要定时执行的方法。


深度解析

1. 使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 1. 开启定时任务(启动类或配置类)
@SpringBootApplication
@EnableScheduling // 开启定时任务
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

// 2. 标注定时方法
@Service
public class ScheduledService {

@Scheduled(cron = "0 0 0 * * ?") // 每天凌晨 0 点执行
public void backupDatabase() {
System.out.println("备份数据库...");
}

@Scheduled(fixedRate = 60000) // 每 60 秒执行一次(上一次开始时间算起)
public void heartbeat() {
System.out.println("心跳检测...");
}

@Scheduled(fixedDelay = 60000) // 每 60 秒执行一次(上一次结束时间算起)
public void cleanup() {
System.out.println("清理临时文件...");
}
}

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
2
3
4
5
6
7
8
9
10
11
12
13
# application.yml
spring:
datasource:
primary:
jdbc-url: jdbc:mysql://localhost:3306/primary_db
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
secondary:
jdbc-url: jdbc:mysql://localhost:3306/secondary_db
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1. 主数据源配置
@Configuration
@MapperScan(basePackages = "com.example.mapper.primary", sqlSessionFactoryRef = "primarySqlSessionFactory")
public class PrimaryDataSourceConfig {

@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}

@Bean(name = "primarySqlSessionFactory")
public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
return bean.getObject();
}
}

// 2. 从数据源配置(类似,略)

面试加分回答

「多数据源需要配置多个 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Service
public class AService {
@Lazy // 延迟注入(第一次用时才注入)
@Autowired
private BService bService;

public void doSomething() {
// 只有这里用到 bService 时,才会创建 BService
bService.doSomething();
}
}

@Component
@Lazy // 延迟初始化(第一次注入时才创建)
public class SlowService {
public SlowService() {
System.out.println("创建耗时 Bean...");
try { Thread.sleep(3000); } catch (Exception e) {}
}
}

3. 解决循环依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class AService {
@Lazy // ✅ 用 @Lazy 解决循环依赖
@Autowired
private BService bService;
}

@Service
public class BService {
@Lazy // ✅ 用 @Lazy 解决循环依赖
@Autowired
private AService aService;
}

面试加分回答

「@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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
public class MyAutoConfiguration {

@Bean
@ConditionalOnClass(name = "com.example.SomeClass") // 类路径有 SomeClass 才创建这个 Bean
public MyService myService() {
return new MyService();
}

@Bean
@ConditionalOnMissingBean // 容器中没有 MyService 才创建(用户可以自定义)
public MyService defaultMyService() {
return new DefaultMyService();
}
}

面试加分回答

「@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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Service
@Scope("singleton") // 默认,可以不写
public class UserService {
// 整个 Spring 容器只有一个实例
}

@Component
@Scope("prototype") // 每次注入都创建新实例
public class PrototypeBean {
// 每次注入都是新的对象
}

// 测试
@Autowired
private PrototypeBean bean1;

@Autowired
private PrototypeBean bean2;

System.out.println(bean1 == bean2); // false(每次都是新实例)

3. 单例 Bean 的线程安全问题

1
2
3
4
5
6
7
8
@Service
public class UserService {
private int count = 0; // ❌ 成员变量,线程不安全

public void increment() {
count++; // 多个线程同时调用会有问题
}
}

解决方案:

  1. 不用成员变量(无状态设计,推荐)
  2. ThreadLocal
  3. synchronized(性能差)

面试加分回答

「Spring Bean 默认是单例(singleton),还有 prototype、request、session 等作用域。单例 Bean 不是线程安全的,如果有成员变量会有并发问题。实际项目中,Service 和 Dao 一般都是无状态的(没有成员变量),所以是线程安全的。如果有状态需求,可以用 prototype 作用域。」


Q44:Spring 中的 BeanFactory 和 ApplicationContext 有什么区别?

一句话结论

BeanFactory 是 Spring 的基础设施(延迟加载),ApplicationContext 是 BeanFactory 的超集(启动时加载所有 Bean)。


深度解析

1. BeanFactory(基础版)

1
2
3
// BeanFactory(延迟加载,用的时候才创建 Bean)
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
UserService userService = beanFactory.getBean(UserService.class); // 这里才创建 Bean

2. ApplicationContext(增强版,推荐)

1
2
3
// ApplicationContext(启动时加载所有 Bean)
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean(UserService.class); // Bean 已经创建好了

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
2
3
4
5
6
7
8
9
# application.yml(简单配置)
logging:
level:
root: INFO # 根日志级别
com.example: DEBUG # 指定包日志级别
file:
name: logs/app.log # 日志文件路径
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n" # 控制台日志格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- logback-spring.xml(高级配置,推荐) -->
<configuration>
<!-- 控制台 appender -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

<!-- 文件 appender -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory> <!-- 保留 30 天 -->
</rollingPolicy>
</appender>

<!-- 日志级别 -->
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</configuration>

面试加分回答

「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.ymlapplication.properties
如果你的配置写在别的文件(比如 custom.properties),Spring 不知道,需要用 @PropertySource 告诉它。

2. 使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
@PropertySource("classpath:custom.properties") // 加载 custom.properties
public class CustomConfig {

@Value("${custom.name}")
private String name; // 从 custom.properties 中读取 custom.name

@Bean
@ConfigurationProperties(prefix = "custom")
public CustomProperties customProperties() {
return new CustomProperties();
}
}
1
2
3
# custom.properties
custom.name=张三
custom.age=25

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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component
public class CustomHealthIndicator implements HealthIndicator {

@Override
public Health health() {
// 检查第三方服务是否可用
if (isThirdPartyServiceUp()) {
return Health.up()
.withDetail("service", "第三方服务正常")
.withDetail("response_time", "200ms")
.build();
} else {
return Health.down()
.withDetail("service", "第三方服务不可用")
.build();
}
}

private boolean isThirdPartyServiceUp() {
// 实际项目中,这里会调用第三方服务的健康检查接口
return true;
}
}

2. 自定义端点(Endpoint)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component
@Endpoint(id = "custom") // 端点 ID(访问路径:/actuator/custom)
public class CustomEndpoint {

@ReadOperation // GET 请求
public Map<String, Object> custom() {
Map<String, Object> result = new HashMap<>();
result.put("custom", "自定义端点");
result.put("timestamp", System.currentTimeMillis());
return result;
}

@WriteOperation // POST 请求
public void update(@Selector String name, String value) {
// 处理写操作
System.out.println("更新:" + name + " = " + value);
}
}

面试加分回答

「自定义监控端点有两种方式: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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Controller
public class UserController {

@InitBinder // 自定义数据绑定规则
public void initBinder(WebDataBinder binder) {
// 1. 注册自定义编辑器(字符串 → 日期)
binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));

// 2. 设置允许绑定的字段(防止恶意绑定)
binder.setAllowedFields("name", "age", "email");

// 3. 设置不允许绑定的字段(防止恶意绑定)
binder.setDisallowedFields("id", "password");
}

@PostMapping("/users")
public String createUser(User user) {
// WebDataBinder 会把 HTTP 参数绑定到 User 对象上
System.out.println(user.getName()); // 自动转换类型
return "success";
}
}

面试加分回答

「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
3
4
5
6
7
8
9
10
11
// classpath:banner.txt
___ ___ __ __ __ ___ __ __ ___ __ __ __ __ __
| | || | | |__|| || | |__|| | | | | || | | ||
| | || | | | || || | | || | | | | || | | ||
|___|___||_____||__||___||__| |__||___| |__| |__||__| |__||__|


${AnsiColor.BRIGHT_RED}
Spring Boot ${spring-boot.version}
${AnsiColor.BRIGHT_YELLOW}
Project: ${application.title}

2. 关闭 Banner

1
2
3
4
# application.yml
spring:
main:
banner-mode: "off" # 关闭 Banner

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
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service
public class UserService {

public void saveUser(User user) {
saveUserInner(user); // ❌ 自调用,AOP 失效(事务不回滚)
}

@Transactional
public void saveUserInner(User user) {
userDao.save(user);
throw new RuntimeException("保存失败");
// 事务不会回滚!因为自调用绕过了代理对象
}
}

解决方式

  1. 用 AspectJ 替代 Spring AOP
  2. 把方法放到另一个 Service
  3. 用 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 面试不再慌!

建议你按以下顺序复习:

  1. IOC + DI(控制反转、依赖注入、@Autowired vs @Resource)
  2. Bean 生命周期 + 循环依赖(三级缓存,面试超高频)
  3. AOP(动态代理、通知类型、切面顺序)
  4. Spring MVC(DispatcherServlet、工作流程)
  5. Spring Boot 自动配置原理(@SpringBootApplication、条件注解)
  6. 事务管理(传播机制、隔离级别、@Transactional 失效场景)
  7. Spring Security(认证流程、JWT 集成)
  8. Spring Cloud(服务注册与发现、熔断器、API 网关)

祝你面试顺利!🚀

Q36:Spring 中的 @Lazy 注解有什么作用?

一句话总结@Lazy 用于延迟初始化 Bean,只有在第一次被使用时才会创建 Bean 实例,解决循环依赖和启动速度问题。

深度解析

Spring 容器启动时,默认会立即初始化所有单例 Bean。但有时我们希望:

  • Bean 的创建成本很高(比如需要加载大文件、建立网络连接)
  • Bean 可能永远不会被使用(比如某些条件化的 Bean)
  • 存在循环依赖问题

@Lazy 可以标注在:

  • @Component 类上:延迟初始化这个类
  • @Bean 方法上:延迟初始化这个 Bean
  • @Autowired 字段/方法上:延迟注入这个依赖

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Lazy
@Component
public class ExpensiveBean {
public ExpensiveBean() {
System.out.println("ExpensiveBean 被创建了!");
}
}

@SpringBootTest
class LazyTest {
@Lazy // 这里也加 @Lazy,表示延迟注入
@Autowired
private ExpensiveBean expensiveBean;

@Test
void testLazy() {
// 到这里才会创建 ExpensiveBean
expensiveBean.doSomething();
}
}

面试加分回答

  • 可以讲讲 @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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
@DependsOn("configManager") // 确保 ConfigManager 先被初始化
public class ServiceA {
@PostConstruct
public void init() {
// 这里可以安全地使用 ConfigManager 创建的配置
String config = ConfigManager.getConfig();
}
}

@Component
public class ConfigManager {
@PostConstruct
public void init() {
// 初始化配置
}
}

面试加分回答

  • 可以讲讲 @DependsOn@Lazy 的区别(@DependsOn 是控制初始化顺序,@Lazy 是延迟初始化)
  • 可以讲讲实际应用场景(比如数据库迁移脚本需要在 Service 之前执行)

Q38:Spring 中的 @Primary 和 @Qualifier 有什么区别?

一句话总结@Primary 用于指定首选 Bean@Qualifier 用于精确指定要注入的 Bean。

深度解析

当容器中有多个同类型的 Bean 时,Spring 会抛出 NoUniqueBeanDefinitionException。解决方案有:

  1. @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;
    }
  2. @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
2
3
4
5
6
7
8
9
10
11
@Configuration
@ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver") // MySQL 驱动存在时
@ConditionalOnProperty(prefix = "spring.datasource", name = "url") // 配置了 url
public class DataSourceAutoConfiguration {

@Bean
@ConditionalOnMissingBean // 容器中还没有 DataSource 时
public DataSource dataSource() {
return new HikariDataSource();
}
}

面试加分回答

  • 可以讲讲如何自定义 @Conditional 注解(实现 Condition 接口)
  • 可以讲讲 Spring Boot 自动配置的加载原理(META-INF/spring.factoriesMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  • 可以讲讲实际应用场景(比如根据配置文件切换不同的实现类)

Q40:Spring 中的 BeanPostProcessor 和 BeanFactoryPostProcessor 有什么区别?

一句话总结BeanPostProcessorBean 初始化前后执行,BeanFactoryPostProcessorBean 定义加载完成后、Bean 实例化前执行。

深度解析

Spring 容器启动流程:

  1. 加载 Bean 定义(BeanDefinition
  2. 执行 BeanFactoryPostProcessor(可以修改 Bean 定义)
  3. 实例化 Bean
  4. 执行依赖注入(@Autowired
  5. 执行 BeanPostProcessor#postProcessBeforeInitialization
  6. 执行初始化方法(@PostConstructInitializingBean
  7. 执行 BeanPostProcessor#postProcessAfterInitialization

BeanFactoryPostProcessor 示例

1
2
3
4
5
6
7
8
9
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// 可以修改 Bean 定义
BeanDefinition bd = beanFactory.getBeanDefinition("myBean");
bd.setPropertyValues(new MutablePropertyValues().add("name", "newValue"));
}
}

BeanPostProcessor 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
System.out.println("Bean " + beanName + " 初始化前...");
return bean; // 可以返回代理对象
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean " + beanName + " 初始化后...");
return bean;
}
}

面试加分回答

  • 可以讲讲 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Aspect
@Component
public class LoggingAspect {

@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("方法执行前:" + joinPoint.getSignature().getName());
}

@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
System.out.println("方法正常返回:" + result);
}

@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
System.out.println("方法抛出异常:" + ex.getMessage());
}

@After("execution(* com.example.service.*.*(..))")
public void logAfter(JoinPoint joinPoint) {
System.out.println("方法执行后");
}

@Around("execution(* com.example.service.*.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕通知 - 方法执行前");
Object result = joinPoint.proceed(); // 执行目标方法
System.out.println("环绕通知 - 方法执行后");
return result;
}
}

面试加分回答

  • 可以讲讲 @Around 的强大之处(可以控制是否执行目标方法、可以修改参数和返回值)
  • 可以讲讲通知的执行顺序(@Around 前部分 → @Before → 目标方法 → @Around 后部分 → @After@AfterReturning/@AfterThrowing
  • 可以讲讲实际应用场景(日志、事务、权限校验、接口耗时统计)

Q42:Spring 中的事务传播行为有哪些?

一句话总结:Spring 定义了 7 种事务传播行为,决定了事务方法嵌套调用时事务如何传播。

深度解析

传播行为 说明
REQUIRED(默认) 有事务就加入,没有就新建
REQUIRES_NEW 挂起当前事务,新建一个事务
SUPPORTS 有事务就加入,没有就以非事务方式执行
NOT_SUPPORTED 以非事务方式执行,挂起当前事务
MANDATORY 必须有事务,否则抛异常
NEVER 必须没有事务,否则抛异常
NESTED 如果当前有事务,就在嵌套事务中执行

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Service
public class OrderService {

@Transactional(propagation = Propagation.REQUIRED)
public void createOrder() {
// 加入当前事务(如果有),否则新建事务
orderMapper.insert(order);
paymentService.pay(); // 调用另一个事务方法
}
}

@Service
public class PaymentService {

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void pay() {
// 挂起 OrderService 的事务,新建一个事务
paymentMapper.insert(payment);
// 如果这里抛异常,只回滚 PaymentService,不影响 OrderService
}
}

面试加分回答

  • 可以讲讲 REQUIRES_NEW 的实际应用场景(比如日志记录,无论主事务是否成功,日志都要记录)
  • 可以讲讲 NESTEDREQUIRES_NEW 的区别(NESTED 是嵌套事务,回滚到保存点;REQUIRES_NEW 是完全独立的事务)
  • 可以讲讲事务传播行为的实现原理(Spring 通过 TransactionInterceptor 拦截方法调用,根据传播行为决定是否创建新事务)

Q43:Spring 中的 @Transactional 注解有哪些注意事项?

一句话总结@Transactional 注解有很多,比如非public方法不生效自调用不生效异常类型不对不回滚等。

深度解析

常见坑

  1. 只有public方法才生效

    1
    2
    3
    4
    5
    6
    7
    @Service
    public class UserService {
    @Transactional
    private void updateUser() { // ❌ private 方法,事务不生效!
    userMapper.update(user);
    }
    }
  2. 自调用问题

    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 是通过代理对象实现的,自调用绕过了代理对象。

  3. 异常类型不对不回滚

    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)

  4. 数据库引擎不支持事务

    • 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component
@Endpoint(id = "custom") // 端点路径:/actuator/custom
public class CustomEndpoint {

@ReadOperation // GET 请求
public Map<String, Object> read() {
Map<String, Object> result = new HashMap<>();
result.put("custom", "Hello Actuator!");
return result;
}

@WriteOperation // POST 请求
public void write(@Selector String name, String value) {
// 可以动态修改配置
}
}

面试加分回答

  • 可以讲讲如何自定义健康检查(实现 HealthIndicator 接口)
  • 可以讲讲如何自定义指标(使用 MeterRegistry
  • 可以讲讲实际应用场景(比如监控业务指标:订单量、支付成功率等)

Q45:Spring Security 中的 CSRF 防护原理是什么?

一句话总结:CSRF(跨站请求伪造)防护通过在表单中嵌入随机 Token 来验证请求是否来自合法页面。

深度解析

CSRF 攻击流程

  1. 用户登录了 good-site.com(合法网站)
  2. 用户访问了 evil-site.com(恶意网站)
  3. 恶意网站自动提交一个表单到 good-site.com(比如转账请求)
  4. 浏览器会自动带上 good-site.com 的 Cookie
  5. good-site.com 以为是用户自己发起的请求,执行了转账

Spring Security 的 CSRF 防护

  1. 服务器生成一个随机 Token,存储在 HttpSession 或 Cookie 中
  2. 在表单中嵌入一个隐藏字段 _csrf
  3. 提交表单时,服务器验证 Token 是否正确

示例

1
2
3
4
5
<form method="post" action="/transfer">
<input type="hidden" name="_csrf" value="a8f3d7e9..." />
转账金额:<input type="text" name="amount" />
<input type="submit" value="转账" />
</form>

面试加分回答

  • 可以讲讲为什么 REST API 通常不需要 CSRF 防护(因为 REST API 使用 Token 认证,而不是 Cookie)
  • 可以讲讲 CSRF Token 的存储方式(HttpSession、Cookie、Double Submit Cookie)
  • 可以讲讲实际应用场景(比如银行系统、支付系统必须开启 CSRF 防护)

Q46:Spring Cloud Gateway 的过滤器有哪些类型?

一句话总结:Spring Cloud Gateway 提供了全局过滤器路由过滤器,可以在请求转发前后执行逻辑(比如鉴权、限流、日志)。

深度解析

过滤器类型

  1. 全局过滤器(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; // 优先级,数字越小优先级越高
    }
    }
  2. 路由过滤器(GatewayFilter):作用于指定路由

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    spring:
    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地址来调用服务。

深度解析

服务发现流程

  1. 服务注册:服务启动时,向注册中心注册自己的地址(IP、端口、服务名)
  2. 服务订阅:客户端从注册中心获取服务实例列表
  3. 健康检查:注册中心定期检测服务实例是否健康
  4. 服务下线:服务关闭时,向注册中心注销自己

常见注册中心

注册中心 一致性协议 健康检查 适用场景
Eureka AP(高可用) 客户端心跳 Spring Cloud Netflix
Nacos AP/CP(可切换) 客户端心跳 + 服务端探测 Spring Cloud Alibaba
Consul CP(强一致) 服务端探测 多数据中心
Zookeeper CP(强一致) 会话机制 Dubbo

示例(Eureka)

1
2
3
4
5
6
7
8
9
10
11
# 服务端配置
eureka:
client:
register-with-eureka: false # 不注册自己
fetch-registry: false

# 客户端配置
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/

面试加分回答

  • 可以讲讲 CAP 理论(一致性、可用性、分区容错性,三者不可兼得)
  • 可以讲讲 Eureka 的自我保护机制(网络分区时,不剔除不健康实例)
  • 可以讲讲实际选型建议(内网用 Nacos,跨机房用 Consul)

Q48:Spring Cloud 中的负载均衡原理是什么?

一句话总结:负载均衡通过负载均衡算法从服务实例列表中选择一个实例,常见的有轮询、随机、加权轮询等。

深度解析

负载均衡类型

  1. 服务端负载均衡(Nginx、F5)

    • 客户端不知道有多个服务实例
    • 负载均衡由反向代理服务器完成
  2. 客户端负载均衡(Ribbon、LoadBalancer)

    • 客户端从注册中心获取服务实例列表
    • 负载均衡由客户端完成

Ribbon 负载均衡算法

  • 轮询(RoundRobin)
  • 随机(Random)
  • 重试(Retry)
  • 加权响应时间(WeightedResponseTime)

Spring Cloud LoadBalancer(推荐):

1
2
3
4
5
6
7
@Bean
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(
Environment environment, LoadBalancerClientFactory factory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RoundRobinLoadBalancer(
factory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}

面试加分回答

  • 可以讲讲 Ribbon 和 LoadBalancer 的区别(Ribbon 已进入维护模式,推荐使用 LoadBalancer)
  • 可以讲讲如何自定义负载均衡算法(比如根据机房就近选择)
  • 可以讲讲实际应用场景(比如灰度发布,根据版本号路由)

Q49:Spring Cloud 中的熔断原理是什么?

一句话总结:熔断通过监控服务调用失败率,当失败率达到阈值时自动切断调用,避免雪崩效应。

深度解析

熔断器状态

  1. Closed(关闭):正常状态,请求可以访问服务
  2. Open(打开):失败率达到阈值,请求直接失败(不走网络)
  3. Half-Open(半开):经过一段时间,放行部分请求,如果成功则关闭熔断器

Hystrix 示例(已进入维护模式):

1
2
3
4
5
6
7
8
9
10
11
@Service
public class UserService {
@HystrixCommand(fallbackMethod = "getDefaultUser")
public User getUser(Long id) {
return restTemplate.getForObject("http://user-service/user/" + id, User.class);
}

public User getDefaultUser(Long id) {
return new User(id, "默认用户");
}
}

Resilience4j 示例(推荐):

1
2
3
4
5
6
7
8
9
10
11
@Service
public class UserService {
@CircuitBreaker(name = "userService", fallbackMethod = "getDefaultUser")
public User getUser(Long id) {
return restTemplate.getForObject("http://user-service/user/" + id, User.class);
}

public User getDefaultUser(Long id, Exception ex) {
return new User(id, "默认用户");
}
}

面试加分回答

  • 可以讲讲 Hystrix 和 Resilience4j 的区别(Hystrix 已进入维护模式,推荐使用 Resilience4j)
  • 可以讲讲熔断和降级的区别(熔断是自动切断调用,降级是返回兜底结果
  • 可以讲讲实际应用场景(比如调用第三方接口时,一定要加熔断)

Q50:Spring Cloud 中的配置中心原理是什么?

一句话总结:配置中心将配置文件集中管理,支持动态刷新,避免修改配置后重启服务。

深度解析

配置中心流程

  1. 配置中心服务端存储配置文件(Git、数据库、本地文件)
  2. 客户端启动时,从配置中心拉取配置
  3. 配置中心配置变更时,客户端收到通知并刷新配置

Spring Cloud Config 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 服务端配置
spring:
cloud:
config:
server:
git:
uri: https://github.com/your-repo/config-repo

# 客户端配置
spring:
application:
name: user-service
cloud:
config:
uri: http://localhost:8888
label: master

动态刷新

1
2
3
4
5
6
7
8
9
10
11
@RestController
@RefreshScope // 配置刷新时,重新创建这个 Bean
public class ConfigController {
@Value("${custom.config}")
private String config;

@GetMapping("/config")
public String getConfig() {
return config;
}
}

面试加分回答

  • 可以讲讲 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 与微服务构建》- 方志朋

💪 最后的建议

  1. 不要急于求成,要打好基础
  2. 多写代码,光看不练是没用的
  3. 看源码,理解 Spring 的设计思想
  4. 做项目,在实际项目中应用 Spring
  5. 教别人,教是最好的学

祝你学习顺利!🎉



Spring 面试八股文 50 道|深度详解版(傻子都能看懂)
https://whyalwaysme.lol/2026/06/07/2026-06-07-spring-interview-deep/
作者
Cassiur
发布于
2026年6月7日
许可协议