Spring Boot 高频面试题汇总
第一部分:基础入门
Q1:@SpringBootApplication 注解做了什么?
参考答案:
@SpringBootApplication 是一个组合注解,包含三个核心注解:
@SpringBootApplication
// 等价于
@Configuration // 标记为配置类
@EnableAutoConfiguration // 启用自动配置
@ComponentScan // 组件扫描(默认扫描同包及子包)
@EnableAutoConfiguration 内部:
@AutoConfigurationPackage // 记录主类所在包
@Import(AutoConfigurationImportSelector.class) // 导入自动配置类
Q2:自动配置原理?
参考答案:
-
@EnableAutoConfiguration 通过
@Import导入AutoConfigurationImportSelector -
selectImports() 方法执行流程:
// 1. 从 spring.factories 读取所有候选配置 List<String> configurations = SpringFactoriesLoader .loadFactoryNames(EnableAutoConfiguration.class, classLoader); // 2. 根据 @Conditional 条件过滤 configurations = configurations.stream() .filter(ConfigurationClassFilter.filter()) .collect(Collectors.toList()); // 3. 按需配置 return configurations.toArray(new String[0]); -
按需配置:Spring Boot 只加载符合条件的配置类
Q3:Spring Boot 和 Spring MVC 的区别?
参考答案:
| 维度 | Spring MVC | Spring Boot |
|---|---|---|
| 定位 | Web 框架 | 应用框架 |
| 配置 | XML/注解配置 | 自动配置 |
| 依赖 | 手动管理 | 起步依赖 |
| 部署 | WAR 到 Tomcat | JAR 内嵌容器 |
| 监控 | 无 | Actuator |
第二部分:核心机制
Q4:IoC 和 DI 的区别?
参考答案:
| 概念 | 说明 |
|---|---|
| IoC(控制反转) | 将对象创建/管理权交给容器 |
| DI(依赖注入) | 容器在运行时注入依赖 |
DI 是实现 IoC 的一种方式
// 传统:自己创建
class UserService {
private UserRepository repo = new UserRepositoryImpl(); // 主动控制
}
// IoC/DI:容器注入
class UserService {
private UserRepository repo; // 被动接收
UserService(UserRepository repo) { this.repo = repo; }
}
Q5:Bean 的生命周期?
参考答案:
1. 实例化 → new 对象
↓
2. 属性赋值 → setXxx()
↓
3. BeanNameAware → setBeanName()
↓
4. BeanFactoryAware → setBeanFactory()
↓
5. ApplicationContextAware → setApplicationContext()
↓
6. BeanPostProcessor.postProcessBeforeInitialization()
↓
7. @PostConstruct → 初始化方法
↓
8. InitializingBean.afterPropertiesSet()
↓
9. 自定义 init-method
↓
10. BeanPostProcessor.postProcessAfterInitialization()
↓
11. Bean 就绪
↓
12. @PreDestroy → 销毁前回调
↓
13. DisposableBean.destroy()
↓
14. 自定义 destroy-method
Q6:Spring AOP 的代理机制?
参考答案:
| 代理方式 | 条件 | 原理 |
|---|---|---|
| JDK 动态代理 | 目标类实现接口 | Proxy.newProxyInstance |
| CGLIB 代理 | 未实现接口/配置强制使用 | 继承目标类生成子类 |
// 代理对象调用流程
proxy.save(); // 调用
↓
JdkDynamicAopProxy.invoke() // 拦截
↓
MethodInterceptor.before()
↓
target.save(); // 目标方法
↓
MethodInterceptor.after()
Spring Boot 2.x 默认使用 CGLIB
Q7:@Transactional 失效场景?
参考答案:
| 场景 | 原因 | 解决 |
|---|---|---|
| private 方法 | AOP 不支持 | 改为 public |
| 自调用 | 不走代理 | 注入自身代理 |
| 异常被 catch | 异常未抛出 | 重新抛出 |
| 非 RuntimeException | 默认只回滚 Runtime | rollbackFor 指定 |
| 非 Spring 管理的 Bean | 没有代理 | @Service 管理 |
| 多数据源 | 事务管理器未指定 | 指定 transactionManager |
Q8:循环依赖如何解决?
参考答案:
Spring 处理循环依赖的三级缓存:
// 一级缓存:成品 Bean
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<>();
解决流程:
A 创建 → 需要 B → 去拿 B
B 创建 → 需要 A → 去拿 A
A 已创建(半成品) → 返回 A
B 完成 → 返回 A
A 完成
无法解决的场景:
- 构造器循环依赖
- prototype 作用域循环依赖
第三部分:Web 开发
Q9:GET 和 POST 的区别?
参考答案:
| 维度 | GET | POST |
|---|---|---|
| 参数位置 | URL | Body |
| 长度限制 | ~2KB | 无 |
| 缓存 | 可缓存 | 不缓存 |
| 幂等 | 幂等 | 非幂等 |
| 安全性 | 参数暴露 | 相对安全 |
Q10:拦截器和过滤器的区别?
参考答案:
| 维度 | Filter | Interceptor |
|---|---|---|
| 来源 | Servlet API | Spring MVC |
| 执行位置 | DispatcherServlet 前 | Controller 前 |
| 作用范围 | 所有请求 | Controller 层 |
| 获取 Spring Bean | 否 | 是 |
| 典型场景 | 字符编码、CORS | 认证、日志 |
Q11:异常处理流程?
参考答案:
Controller 抛出异常
↓
DispatcherServlet 捕获
↓
HandlerExceptionResolver 处理
↓
@ExceptionHandler 方法
↓
返回错误响应
第四部分:数据访问
Q12:MyBatis #{} 和 ${} 的区别?
参考答案:
| 特性 | #{} | ${} |
|---|---|---|
| SQL | 预编译参数占位 | 字符串拼接 |
| 注入 | 防注入 | 可能注入 |
| 类型 | 自动转换 | 直接替换 |
Q13:Redis 缓存一致性问题?
参考答案:
Cache Aside 模式:
// 读
Cache cache = redis.get(key);
if (cache == null) {
cache = db.get(key);
redis.set(key, cache);
}
// 写
db.update(key, value);
redis.delete(key); // 删除缓存,不是更新
延迟双删:
redis.delete(key);
db.update(key, value);
Thread.sleep(100);
redis.delete(key); // 延迟再删
Q14:如何解决缓存穿透、击穿、雪崩?
参考答案:
| 问题 | 解决方案 |
|---|---|
| 穿透 | 布隆过滤器 / 空值缓存 |
| 击穿 | 分布式锁 / 热点数据永不过期 |
| 雪崩 | 过期时间随机 / 多级缓存 |
第五部分:微服务
Q15:Nacos AP 和 CP 切换?
参考答案:
# 切换为 CP 模式
curl -X PUT 'http://localhost:8848/nacos/v1/ns/operator/mode?mode=CP'
# 切换为 AP 模式
curl -X PUT 'http://localhost:8848/nacos/v1/ns/operator/mode?mode=AP'
选择建议:
- 服务注册发现 → AP(高可用)
- 配置管理 → CP(强一致)
Q16:Sentinel 和 Hystrix 的区别?
参考答案:
| 维度 | Sentinel | Hystrix |
|---|---|---|
| 隔离策略 | 信号量 | 线程池 |
| 熔断触发 | 慢调用/异常比例 | 异常数/超时 |
| 规则 | 动态推送 | 配置后不可变 |
| 状态 | 活跃 | 停止维护 |
第六部分:综合问题
Q17:Spring Boot 启动流程?
参考答案:
SpringApplication.run()
↓
prepareEnvironment() // 准备环境
↓
createApplicationContext() // 创建上下文
↓
prepareContext() // 预处理上下文
↓
refreshContext() // 刷新上下文
↓
└→ onRefresh() → DispatcherServlet 初始化
↓
afterRefresh() // 刷新后处理
↓
started() // 发布 ApplicationStartedEvent
↓
callRunners() // 执行 ApplicationRunner
Q18:如何排查 Spring Boot 启动失败?
参考答案:
- 查看错误日志:重点关注 Caused by
- 检查端口占用:
netstat -ano | grep 8080 - 检查依赖冲突:
mvn dependency:tree - 检查配置:application.yml 是否正确
- 启用调试日志:
logging: level: org.springframework: DEBUG - 检查自动配置报告:
debug: true
Q19:Spring Boot 2.x 和 3.x 的区别?
参考答案:
| 维度 | Spring Boot 2.x | Spring Boot 3.x |
|---|---|---|
| Java 版本 | 8+ | 17+ |
| Jakarta EE | Java EE 8 | Jakarta EE 9+ |
| 注解包名 | javax.* | jakarta.* |
| 最低 Spring | 5.x | 6.x |
| 建议 | 保守迁移 | 新项目 |
迁移注意:
- 依赖升级到支持 jakarta 的版本
- 注解从 javax 改为 jakarta
- 不兼容的库需要替换
Q20:如何设计一个好的微服务架构?
参考答案:
设计原则:
- 单一职责:每个服务只做一件事
- 松耦合:服务间通过接口通信
- 高内聚:相关功能放在一起
- 独立部署:服务可独立部署升级
核心组件:
├── 网关层 → Gateway、Zuul
├── 注册中心 → Nacos、Eureka
├── 配置中心 → Nacos Config、Apollo
├── 服务调用 → OpenFeign、Dubbo
├── 限流熔断 → Sentinel、Hystrix
├── 链路追踪 → Sleuth、Zipkin
├── 日志监控 → ELK
└── 服务容器 → Docker、K8s
📝 面试题持续更新中,欢迎补充!