Spring & Spring Boot 面试全景指南

高频考点 + 核心机制图解 — 从原理到生产实战

IoC / DI AOP Bean生命周期 循环依赖 事务管理 自动配置 SpringMVC
📚 想要深入理解原理?推荐搭配专题图解学习

本页侧重面试考点速答。若需理解底层原理和运行机制,请看以下专题图解:

📘 Spring深度图解 📘 Spring运行真相

Part 1:Spring Core 核心

IoC容器 · Bean生命周期 · AOP · 循环依赖 · 设计模式

1.1 IoC / DI 控制反转与依赖注入 ⭐⭐⭐⭐⭐

什么是 IoC?什么是 DI?

IoC(Inversion of Control)控制反转:对象的创建和管理权从程序代码转移到 Spring 容器。控制权被"反转"了。

DI(Dependency Injection)依赖注入:IoC 的具体实现方式,容器自动将依赖对象注入到需要的地方。

二者的关系:IoC 是思想,DI 是实现方式

没有 IoC 的世界:每个类自己 new 依赖对象。OrderService 里写 new UserRepo(),换数据库实现要改源码、重新编译
有 IoC 的世界:所有对象由容器创建和管理。OrderService 只声明"我需要 UserRepo"(@Autowired),容器自动注入实现
核心好处:面向接口编程,依赖关系由容器集中管理,更换实现只需改配置不改代码

IoC 容器两大核心接口

对比BeanFactoryApplicationContext
定位 IoC 容器最基础接口 BeanFactory 的超集,高级容器
Bean 加载时机 懒加载(Lazy Loading)
getBean 时才创建
预加载(Eager Loading)
容器启动时就创建所有单例 Bean
国际化支持 ❌ 不支持 ✅ MessageSource
事件发布 ❌ 不支持 ✅ ApplicationEvent
AOP 集成 需手动配置 ✅ 自动集成
资源访问 ❌ 不支持 ✅ ResourceLoader
使用场景 资源受限的环境(移动端等) 绝大多数场景都用这个 ✅

DI 三种注入方式对比

方式示例推荐度原因
构造器注入 构造方法上加 @Autowired ⭐⭐⭐ 首选 依赖不可变(final)、保证不为null、完全初始化、方便单元测试
Setter 注入 setter 方法上加 @Autowired ⭐⭐ 可选依赖 灵活,可重新配置,适合可选依赖
字段注入 字段上直接 @Autowired ⭐ 不推荐 不能声明final、无法脱离IoC容器测试、隐藏依赖关系、Spring官方不推荐
// ✅ 构造器注入(推荐)
@Service
public class OrderService {
  private final UserRepo userRepo;

  @Autowired // Spring 4.3+ 单构造器可省略
  public OrderService(UserRepo userRepo) {
    this.userRepo = userRepo;
  }
}

// ❌ 字段注入(不推荐)
@Service
public class OrderService {
  @Autowired
  private UserRepo userRepo; // 不能 final,无法保证不为null
}

@Autowired vs @Resource 区别(面试超高频!)h3>
对比@Autowired@Resource
来源 Spring 框架注解
org.springframework.beans.factory.annotation
JSR-250 标准(Java)
javax.annotation
注入顺序 先 byType → 再 byName → 还不行就报错 先 byName → 再 byType → 还不行就报错
指定名称 配合 @Qualifier("beanName") @Resource(name="beanName")
required 支持 @Autowired(required=false) 允许注入null ❌ 不支持,找不到就报错
适用范围 构造器、方法、字段、参数 方法、字段

面试必答模板
1. IoC 是什么?→ 控制反转,对象的创建权从程序转移到容器
2. 以前怎么做?→ 自己 new 对象,对象之间耦合度高
3. 现在怎么做?→ Spring 容器统一管理,需要什么注入什么
4. DI 是什么?→ 依赖注入,是 IoC 的实现方式
5. 好处?→ 解耦、方便测试、提高可维护性

IoC = 不用自己 new,容器管一切
DI = 需要什么,容器注入什么
@Autowired 先类型再名称,@Resource 先名称再类型

1.2 Bean 生命周期 ⭐⭐⭐⭐⭐

完整生命周期流程

1. 实例化
createBeanInstance() — 反射创建对象
2. 属性赋值
populateBean() — DI 依赖注入
3. Aware 回调
BeanNameAware / BeanFactoryAware / ApplicationContextAware
4. BeanPostProcessor 前置
postProcessBeforeInitialization()
5. 初始化
@PostConstruct → afterPropertiesSet() → init-method
6. BeanPostProcessor 后置
postProcessAfterInitialization()
🔑 AOP 代理在此生成!
7. 就绪
Bean 可以正常使用了
8. 销毁
@PreDestroy → destroy() → destroy-method

Bean 生命周期核心要点:
📋 实例化:反射调用构造方法,对象在堆中分配内存
💉 属性注入:解析 @Autowired/@Value,完成依赖注入
🔔 Aware 回调:把容器相关对象注入 Bean(如 ApplicationContext)
🔧 前置处理:BeanPostProcessor.postProcessBeforeInitialization()
🏁 初始化:@PostConstruct → InitializingBean → init-method
🎭 后置处理:AOP 代理在这一步生成(关键!)
就绪:放入 singletonObjects,可以被业务使用
🗑️ 销毁:容器关闭时,@PreDestroy → DisposableBean → destroy-method

三种初始化/销毁方式对比

方式初始化销毁执行优先级
JSR-250 注解 @PostConstruct @PreDestroy 最高 (1)
InitializingBean 接口 afterPropertiesSet() destroy() 中间 (2)
@Bean 自定义方法 initMethod="xxx" destroyMethod="xxx" 最低 (3)

面试陷阱:AOP 代理是在 BeanPostProcessor 的后置处理(第6步)中生成的!
这意味着:初始化方法(@PostConstruct)执行时,Bean 还不是代理对象!内部 this 调用不走代理!

面试高频追问:Bean 是线程安全的吗?
→ 不是!Spring Bean 默认 singleton,如果 Bean 有可变状态,多线程并发访问不安全。
解决方案:① 使用 prototype 作用域 ② Bean 设计为无状态 ③ 使用 ThreadLocal

Bean 生命周期记忆口诀:
实例化 → 注入属性 → Aware → 前置 → 初始化 → 后置(AOP) → 使用 → 销毁
关键点:AOP 在后置处理中生成,不是初始化时!

1.3 AOP 面向切面编程 ⭐⭐⭐⭐⭐

什么是 AOP?

AOP(Aspect-Oriented Programming)面向切面编程:将横切关注点(日志、权限、事务)从业务逻辑中分离出来,统一管理。

核心思想:不修改业务代码,却能增强业务功能。

AOP 的本质:横向抽取,统一织入
假设 20 个 Service 方法都需要日志、权限、事务。不用 AOP → 每个方法手写相同的重复代码。
用 AOP → 把这些"横切逻辑"提取到一个切面类,通过切入点表达式指定"哪些方法要增强",运行时由动态代理自动织入。
业务代码完全不知道增强逻辑的存在 —— 这就是"非侵入式增强"。

AOP 核心概念对照表

概念含义生产场景代码体现
Aspect 切面 横切关注点的模块化(日志、事务等) 一个日志切面类,统一处理所有 Service 的日志 @Aspect 类
JoinPoint 连接点 程序执行的某个点(方法调用等) 所有可以被拦截的方法执行点 方法的执行点
Pointcut 切入点 定义在哪些连接点生效(表达式匹配) 指定"只拦截 Service 层所有方法" @Pointcut("execution(* com..*(..))")
Advice 通知 在切入点处做什么(增强逻辑) 方法执行前记录参数、执行后记录耗时 @Before / @After / @Around
Target 目标对象 被代理的原始对象 被增强的 UserService 原始 Bean 原始 Bean
Proxy 代理对象 AOP 创建的增强对象 运行时生成的代理类,包装了原始 Bean + 增强逻辑 JDK / CGLIB 代理
Weaving 织入 将切面应用到目标对象的过程 Spring 在运行时通过动态代理完成织入 运行时动态代理

5 种通知类型对比

通知类型注解执行时机能否获取返回值能否捕获异常
前置通知 @Before 目标方法执行前
返回通知 @AfterReturning 方法正常返回后 ✅ returning 属性
异常通知 @AfterThrowing 方法抛出异常后 ✅ throwing 属性
后置通知 @After 方法执行后(类似 finally)
环绕通知 @Around 包围目标方法(最强大)

执行顺序(Spring 5.2.7+):
@Around(before部分) → @Before → 目标方法 → @AfterReturning / @AfterThrowing → @After → @Around(after部分)

JDK 动态代理 vs CGLIB 代理(面试超高频!)

对比项JDK 动态代理CGLIB 代理
实现原理 基于接口,实现 InvocationHandler 基于继承,生成目标类的子类
核心 API Proxy.newProxyInstance() Enhancer + MethodInterceptor
前提条件 目标类必须实现接口 目标类不能是 final 类
目标方法不能是 final
生成速度 慢(需要生成字节码)
调用性能 稍慢(反射调用) 更快(FastClass 机制)
Spring 默认策略 有接口时使用 JDK 无接口时使用 CGLIB
Spring Boot 默认 ✅ 全部用 CGLIB(2.x 起)
// Spring Boot 2.x 默认使用 CGLIB,如果要用 JDK 动态代理:
@EnableAspectJAutoProxy(proxyTargetClass = false)

// 或在 application.yml 中配置:
spring:
  aop:
    proxy-target-class: false

代码示例:日志切面

@Aspect
@Component
public class LogAspect {

  @Pointcut("execution(* com.example.service.*.*(..))")
  public void serviceLayer() {}

  @Before("serviceLayer()")
  public void before(JoinPoint jp) {
    log.info("调用方法: {}", jp.getSignature().getName());
  }

  @Around("serviceLayer()")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {
    long start = System.currentTimeMillis();
    Object result = pjp.proceed(); // 执行目标方法
    long cost = System.currentTimeMillis() - start;
    log.info("方法 {} 耗时 {}ms", pjp.getSignature().getName(), cost);
    return result;
  }
}

代码示例:权限校验切面

@Aspect
@Component
public class AuthAspect {

  @Around("@annotation(requireRole)")
  public Object checkAuth(ProceedingJoinPoint pjp, RequireRole requireRole) throws Throwable {
    String role = requireRole.value();
    User user = getCurrentUser();
    if (!user.getRoles().contains(role)) {
      throw new AccessDeniedException("权限不足");
    }
    return pjp.proceed();
  }
}

AOP 面试必答模板
1. AOP 是什么?→ 面向切面编程,将横切关注点从业务逻辑中分离
2. 核心原理?→ 动态代理(JDK 或 CGLIB)
3. JDK vs CGLIB?→ JDK 基于接口,CGLIB 基于继承
4. 应用场景?→ 日志、权限、事务、性能监控
5. Spring Boot 默认?→ CGLIB(proxyTargetClass=true)

1.4 循环依赖与三级缓存 ⭐⭐⭐⭐⭐

什么是循环依赖?

A 依赖 B,B 又依赖 A。创建 A 时需要 B,创建 B 时又需要 A,形成死循环。

@Component
public class A {
  @Autowired
  private B b; // A 依赖 B
}

@Component
public class B {
  @Autowired
  private A a; // B 依赖 A
}

循环依赖的本质:A 依赖 B,B 依赖 A。创建 A 时需要 B,创建 B 时又需要 A —— 死循环
Spring 的解法:把"实例化"和"初始化"拆开。A 先实例化(分配内存,半成品),把半成品引用暴露出去。B 创建时能拿到 A 的半成品引用,B 完成初始化后,A 继续拿到完整的 B,流程走通
关键前提:必须能先 new 出一个半成品 → 构造器注入在 new 时就需要依赖,所以无法解决

Spring 三级缓存

级别缓存名称存的内容状态
第一级 singletonObjects 完整的单例 Bean(完全初始化好的) 完全就绪 ✅
第二级 earlySingletonObjects 早期引用(实例化了但还没初始化完) 半成品(有引用,属性未注入)
第三级 singletonFactories ObjectFactory(能生成早期引用的工厂) 工厂延迟生成(按需创建代理)

三级缓存解决循环依赖流程(A → B → A)

Step 1:创建 A
实例化 A(半成品)
Step 2:A 的工厂放入三级缓存
singletonFactories.put(A, () -> getEarlyBeanReference(A))
Step 3:A 属性注入,发现需要 B
去一级缓存找 B → 没有 → 创建 B
Step 4:创建 B
实例化 B → B 的工厂放入三级缓存
Step 5:B 属性注入,发现需要 A
一级没有 → 二级没有 → 三级有!
调用工厂获取 A 的早期引用
放入二级缓存,删除三级缓存
Step 6:B 完成初始化
B 放入一级缓存(完整Bean)
Step 7:A 拿到完整的 B
A 继续初始化,完成
Step 8:A 放入一级缓存
删除二级缓存中的 A

为什么构造器注入不能解决循环依赖?

因为构造器注入在实例化阶段就需要依赖对象,而 Spring 解决循环依赖的前提是:先实例化出一个"半成品"对象(已经 new 出来,只是没初始化完)。构造器注入连 new 都 new 不出来,自然无法解决。

// ❌ 构造器注入的循环依赖 → 启动报错!
@Component
public class A {
  private final B b;
  public A(B b) { this.b = b; } // 构造时就需要 B
}

// ✅ 字段/Setter 注入的循环依赖 → 三级缓存可以解决
@Component
public class A {
  @Autowired
  private B b; // 先 new A,后面再注入 B
}

为什么需要第三级缓存?(而不是两级就够了?)

为了处理 AOP 代理的情况。

如果只有两级缓存:在实例化后就要决定是否创建代理对象。但 AOP 代理本应在 BeanPostProcessor 后置处理中生成(符合生命周期规范)。

有了三级缓存(ObjectFactory):只有在真正被循环依赖引用时,才通过工厂提前生成代理对象。如果没有循环依赖,代理还是在正常的后置处理阶段生成,保证生命周期完整性。

循环依赖面试必答模板
1. 什么是循环依赖?→ A 依赖 B,B 依赖 A
2. Spring 怎么解决?→ 三级缓存
3. 三级缓存分别存什么?→ 一级完整Bean、二级早期引用、三级对象工厂
4. 为什么要三级不是两级?→ 为了处理 AOP,保证代理在正确时机生成
5. 什么情况解决不了?→ 构造器注入的循环依赖、prototype 作用域的循环依赖

三级缓存记忆口诀:
一级存完整,二级存半成品,三级存工厂
解决不了:构造器注入(new 都 new 不出来)
关键点:三级缓存是为了 AOP 代理!

1.5 Spring 中的设计模式 ⭐⭐⭐⭐

Spring 框架的源码中大量运用了经典设计模式。理解这些模式不仅有助于面试,更能帮助你在读 Spring 源码时快速理解设计意图。

Spring 用到的 7 大设计模式

🏭 工厂模式

BeanFactory / ApplicationContext

作用:创建和管理对象,不暴露创建逻辑

Spring 源码体现:BeanFactory 统一创建和管理 Bean,调用方不需要知道创建细节

BeanFactory.getBean("beanName")

👤 单例模式

Bean 默认 scope = singleton

作用:保证一个 Bean 在容器中只有一个实例

Spring 源码体现:每个 Bean 在容器中只有一个实例,通过 singletonObjects 缓存保证

singletonObjects 缓存实现

🎭 代理模式

AOP 动态代理

作用:不修改原对象,增强功能

Spring 源码体现:代理对象包装原始 Bean,外部调用代理 → 增强 + 原始逻辑

JDK Proxy / CGLIB

📋 模板方法模式

JdbcTemplate / RestTemplate

作用:定义算法骨架,子类实现具体步骤

Spring 源码体现:JdbcTemplate 定义了获取连接→执行→关闭的骨架,你只写 RowMapper

AbstractApplicationContext.refresh()

📢 观察者模式

ApplicationEvent + ApplicationListener

作用:事件驱动,一对多通知

Spring 源码体现:ApplicationContext.publishEvent() 发布事件,所有 @EventListener 自动收到

publishEvent() / @EventListener

🔌 适配器模式

HandlerAdapter

作用:统一不同 Handler 的调用方式

Spring 源码体现:不同类型的 Controller(@RequestMapping / HttpRequestHandler)通过各自 Adapter 统一调用

DispatcherServlet 调用 HandlerAdapter

🎯 策略模式

Resource 接口的不同实现

作用:同一接口,不同实现策略,运行时选择

Spring 源码体现:Resource 接口有多种实现(ClassPath/FileSystem/URL),运行时按需选择

ClassPathResource / FileSystemResource / UrlResource — 都是 Resource 接口的实现

Spring 设计模式记忆口诀:
工厂造Bean,单例存一份,代理做增强,模板定流程,观察者发事件,适配器统一调用,策略选实现

设计模式代码示例

// 工厂模式 — BeanFactory.getBean()
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = ctx.getBean(UserService.class); // 工厂帮你创建

// 模板方法 — JdbcTemplate
JdbcTemplate jdbc = new JdbcTemplate(dataSource);
jdbc.query("SELECT * FROM user", (rs, rowNum) -> {
  return new User(rs.getLong("id"), rs.getString("name"));
}); // 模板定义了连接/执行/关闭的骨架,你只写映射逻辑

// 观察者模式 — ApplicationEvent
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
  sendNotification(event.getOrder()); // 订单创建后自动发通知
}

Part 2:Spring Boot 核心

自动配置 · 启动流程 · SpringMVC · 事务管理 · 注解速查

2.1 自动配置原理 ⭐⭐⭐⭐⭐

@SpringBootApplication 注解拆解

@SpringBootApplication // 等价于下面三个注解的组合:

@SpringBootConfiguration   // ← 就是 @Configuration,标记配置类
@EnableAutoConfiguration  // ← 核心!开启自动配置
@ComponentScan          // ← 组件扫描(扫描启动类所在包及子包)

自动配置的本质:约定大于配置
你只要引入 spring-boot-starter-web,Spring Boot 自动帮你注册:
· 内嵌服务器(Embedded Tomcat)
· 前端控制器(DispatcherServlet)
· JSON 序列化器(Jackson)
· 错误处理(BasicErrorController)
这一切都是通过 @ConditionalOnXxx 条件注解实现的 —— classpath 下有相关依赖,就自动注册对应 Bean

自动配置完整流程

@EnableAutoConfiguration
开启自动配置
@Import(AutoConfigurationImportSelector)
导入自动配置选择器
SpringFactoriesLoader
读取 META-INF/spring.factories
加载所有 AutoConfiguration 类
如 DataSourceAutoConfiguration、WebMvcAutoConfiguration...
@Conditional 条件过滤
只有满足条件的 Configuration 才会注册 Bean!
满足条件的 Bean 注册到容器
自动配置完成 ✅

@Conditional 条件注解家族

注解作用示例场景
@ConditionalOnClass classpath 中存在指定类时生效 有 DataSource.class 才配置数据源
@ConditionalOnMissingBean 容器中不存在指定 Bean 时生效 用户没自定义 DataSource,才用默认的
@ConditionalOnBean 容器中存在指定 Bean 时生效 有 DataSource 才配置事务管理器
@ConditionalOnProperty 配置文件中指定属性满足条件时生效 spring.cache.type=redis 才用 Redis 缓存
@ConditionalOnWebApplication 当前是 Web 应用时生效 Web 环境才配置 DispatcherServlet
@ConditionalOnExpression SpEL 表达式为 true 时生效 复杂条件组合判断

自定义 Starter 的步骤(加分项)

  1. 创建 XxxAutoConfiguration 类,配合 @Conditional 条件注解
  2. META-INF/spring.factories 中注册 AutoConfiguration 类
  3. 创建 ConfigurationProperties 类绑定配置属性
  4. 创建 spring-boot-starter-xxx 模块(只做依赖聚合)
// 1. 自动配置类
@Configuration
@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyProperties.class)
public class MyAutoConfiguration {

  @Bean
  @ConditionalOnMissingBean
  public MyService myService(MyProperties props) {
    return new MyService(props.getPrefix());
  }
}

// 2. META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.example.MyAutoConfiguration

自动配置面试必答模板
1. @SpringBootApplication = @Configuration + @EnableAutoConfiguration + @ComponentScan
2. 核心是 @EnableAutoConfiguration,通过 @Import 导入 AutoConfigurationImportSelector
3. 读取 META-INF/spring.factories 中所有 AutoConfiguration 类
4. 通过 @ConditionalOnXxx 条件过滤,满足条件的才注册 Bean
5. 所以"约定大于配置",引入 starter 就自动生效

自动配置记忆口诀:
注解导Import → 读factories → 条件过滤 → 注册Bean
核心思想:约定大于配置

2.2 Spring Boot 启动流程 ⭐⭐⭐⭐

完整启动流程

① main()
入口方法
② SpringApplication.run()
创建 SpringApplication 对象
③ 加载 Initializer & Listener
从 spring.factories 加载
④ 准备环境
读取 application.yml 配置
⑤ printBanner
打印启动 Banner
⑥ createApplicationContext
创建 IoC 容器(AnnotationConfigServletWebServerApplicationContext)
⑦ refreshContext(核心!)
调用 ApplicationContext.refresh()
这是最核心的一步!
⑧ afterRefresh
刷新后处理
⑨ callRunners
执行 CommandLineRunner / ApplicationRunner
⑩ 启动完成 ✅
应用就绪,可以接收请求

refresh() 做了什么?(核心中的核心)

步骤方法作用
1 prepareRefresh() 刷新前准备,记录启动时间,设置状态标志
2 obtainFreshBeanFactory() 创建/获取 BeanFactory,加载 BeanDefinition
3 prepareBeanFactory() 配置 BeanFactory 的标准特性(ClassLoader、PostProcessor等)
4 🔥 核心 invokeBeanFactoryPostProcessors() 执行 BeanFactoryPostProcessor,包括扫描 @Component 注册 BeanDefinition、处理 @Conditional 过滤自动配置
5 registerBeanPostProcessors() 注册 BeanPostProcessor(AOP代理、@Autowired注入等)
6 initMessageSource() 初始化国际化资源
7 initApplicationEventMulticaster() 初始化事件广播器
8 🔥 关键 finishBeanFactoryInitialization() 实例化所有非懒加载的单例 Bean!这一步最耗时
9 finishRefresh() 发布 ContextRefreshedEvent,完成刷新

启动流程的本质:初始化 IoC 容器 + 实例化所有 Bean
① main() 入口 → ② SpringApplication.run() 创建启动对象 → ③ 从 spring.factories 加载 Initializer/Listener → ④ 读取 application.yml 准备环境 → ⑤ 打印 Banner → ⑥ 创建 ApplicationContext → ⑦ refresh()(最核心!扫描注册+实例化所有 Bean) → ⑧ afterRefresh 后处理 → ⑨ callRunners 执行自定义 Runner → ⑩ 应用就绪 ✅

面试加分:说出 finishBeanFactoryInitialization() 是最耗时的一步,因为它要实例化所有单例 Bean。可以通过懒加载(@Lazy)优化启动速度。

CommandLineRunner vs ApplicationRunner

对比CommandLineRunnerApplicationRunner
参数类型 String... args ApplicationArguments args
参数解析 原始字符串数组,需要自己解析 封装好的参数对象,提供getOptionValues等方法
执行时机 容器刷新完成后 容器刷新完成后(稍早于CommandLineRunner)
使用场景 简单的启动后任务 需要解析命令行参数的启动任务
@Component
public class MyRunner implements CommandLineRunner {

  @Override
  public void run(String... args) {
    log.info("应用启动完成!参数: {}", Arrays.toString(args));
    // 可以在这里做初始化工作:预热缓存、加载配置等
  }
}

2.3 SpringMVC 请求处理流程 ⭐⭐⭐⭐

HTTP 请求的完整旅行

HTTP 请求
用户发起请求
DispatcherServlet
前端控制器(总调度)
HandlerMapping
根据 URL 找到对应的 Controller 方法
HandlerAdapter
适配调用 Controller 方法
Controller
执行业务逻辑,返回结果
ViewResolver / @ResponseBody
视图解析 或 直接返回JSON
HTTP 响应
返回给用户

SpringMVC 的核心:前端控制器模式(Front Controller Pattern)
所有 HTTP 请求统一入口 DispatcherServlet,由它负责:路由查找 → 适配调用 → 结果处理。
这个模式把请求处理流程标准化,开发者只需关注 Controller 中的业务逻辑,路由、参数绑定、视图渲染由框架完成。

@RequestBody / @ResponseBody / @RestController 的关系

注解作用使用位置
@RequestBody 将 HTTP 请求体的 JSON → Java 对象(反序列化) 方法参数上
@ResponseBody 将 Java 对象 → HTTP 响应体的 JSON(序列化) 方法上或类上
@RestController = @Controller + @ResponseBody(类上所有方法都返回JSON) 类上
@RestController // = @Controller + @ResponseBody
@RequestMapping("/api/users")
public class UserController {

  @GetMapping("/{id}")
  public User getUser(@PathVariable Long id) { // 路径参数
    return userService.getById(id); // 自动转JSON返回
  }

  @PostMapping
  public User createUser(@RequestBody User user) { // JSON → User对象
    return userService.save(user);
  }
}

拦截器(Interceptor) vs 过滤器(Filter)

对比Filter 过滤器HandlerInterceptor 拦截器
规范 Servlet 规范 Spring MVC 规范
作用范围 所有请求(包括静态资源) 只拦截 Controller 方法
执行时机 DispatcherServlet 之前 DispatcherServlet 之后、Controller 之前
能否获取 Handler 信息 ❌ 不能 ✅ 能获取 Controller、方法名等
能否注入 Spring Bean 需要额外配置 ✅ 直接 @Autowired
典型用途 编码转换、XSS过滤、全局参数处理 登录校验、权限检查、日志记录
执行顺序 先 Filter 后 Interceptor

完整执行链:Filter → DispatcherServlet → Interceptor.preHandle → Controller → Interceptor.postHandle → View渲染 → Interceptor.afterCompletion → Filter

SpringMVC 流程记忆:
请求 → 前端控制器(总调度) → 处理器映射(找方法) → 适配器(调方法) → Controller → 视图/JSON → 响应
@RestController = @Controller + @ResponseBody

2.4 事务管理 @Transactional ⭐⭐⭐⭐⭐

@Transactional 底层原理

核心:基于 AOP 动态代理

Spring 通过 AOP 在方法执行前开启事务,方法正常返回时提交事务,抛出异常时回滚事务。

// @Transactional 注解的本质(伪代码)
public Object invoke(MethodInvocation mi) {
  Connection conn = dataSource.getConnection();
  conn.setAutoCommit(false); // 开启事务
  try {
    Object result = mi.proceed(); // 执行业务方法
    conn.commit(); // 提交事务
    return result;
  } catch (RuntimeException e) {
    conn.rollback(); // 回滚事务
    throw e;
  }
}

事务的本质:ACID 原子性
一个业务操作包含多条 SQL(扣库存 + 扣余额 + 创建订单),它们要么全部成功提交,要么全部回滚。绝不能出现"扣了库存但没创建订单"的中间状态。这就是事务的原子性(Atomicity)。

7 种事务传播机制(面试高频!)

REQUIRED
(默认)⭐
有事务就加入,没有就新建
场景:业务方法 A 调用 B,两者在同一个事务中,任一失败全部回滚
SUPPORTS
有事务就加入,没有就非事务执行
场景:查询方法,有没有事务都能跑
MANDATORY
必须在事务中调用,否则报错
场景:强制要求调用方开启事务的核心写操作
REQUIRES_NEW
(高频区分!)⭐
总是新建事务,挂起当前事务
场景:日志记录 —— 主业务失败也要记录操作日志,互不影响
NOT_SUPPORTED
以非事务方式执行,挂起当前事务
场景:批量耗时操作,避免长时间占用数据库连接
NEVER
以非事务方式执行,如果当前有事务则报错
场景:明确不能在事务中执行的操作(如调用外部不支持事务的资源)
NESTED
嵌套事务(Savepoint),外层回滚时内层也回滚
场景:主流程中包含可选子流程,子流程失败不影响主流程

REQUIRED vs REQUIRES_NEW 区别(面试超高频区分!)h3>
对比REQUIRED(默认)REQUIRES_NEW
行为 有事务就加入,共用同一个事务 总是新建独立事务,挂起原来的
回滚影响 任一方法异常,整个事务回滚 内层事务独立,不影响外层
事务关系 共用同一个事务,同生共死 两个独立事务,互不影响
使用场景 大部分业务方法 日志记录(不影响主业务)、独立子流程

事务失效的 5 大场景(面试超高频!)h3>
1
方法不是 public

Spring AOP 默认只对 public 方法生效。protected/private/package-visible 方法加 @Transactional 不生效。

2
同一个类内部调用(this.method())

内部调用走的是 this(原始对象),不走代理对象,所以 AOP 不生效。这是最常考的失效场景!

@Service
public class OrderService {
  public void createOrder() {
    this.saveOrder(); // ❌ 事务不生效!走的是 this,不走代理
  }

  @Transactional
  public void saveOrder() { /* ... */ }
}

解决方案:① 注入自身 ② AopContext.currentProxy() ③ 拆到另一个 Service

3
异常被 try-catch 吞了没抛出

方法内 catch 了异常但没有 re-throw,Spring 以为方法正常返回,就提交了事务。

@Transactional
public void saveOrder() {
  try {
    orderDao.insert(order); // 异常发生
  } catch (Exception e) {
    log.error("出错了", e); // ❌ 异常被吞了,事务不回滚!
  }
}

解决方案:catch 后再 throw,或手动 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()

4
默认只回滚 RuntimeException,checked 异常不回滚

Spring 默认只对 RuntimeException 和 Error 回滚。抛出 IOException 等 checked 异常不会回滚。

解决方案:@Transactional(rollbackFor = Exception.class) 指定回滚所有异常

5
数据库引擎不支持事务

MySQL 的 MyISAM 引擎不支持事务!必须用 InnoDB 引擎。

检查:SHOW CREATE TABLE xxx; 确认 ENGINE=InnoDB

事务隔离级别

隔离级别脏读不可重复读幻读说明
READ_UNCOMMITTED ❌ 可能 ❌ 可能 ❌ 可能 最低级别,基本不用
READ_COMMITTED ✅ 避免 ❌ 可能 ❌ 可能 Oracle 默认
REPEATABLE_READ ✅ 避免 ✅ 避免 ⚠️ 可能 MySQL 默认 ⭐
SERIALIZABLE ✅ 避免 ✅ 避免 ✅ 避免 最高级别,性能最差

记忆方法:隔离级别从低到高,安全性越来越高,但性能越来越差。
MySQL 默认 REPEATABLE_READ,实际通过 MVCC + Next-Key Lock 基本解决了幻读问题。

事务失效总结(面试必背):
① 非public方法 ② 内部this调用 ③ 异常被catch吞 ④ checked异常默认不回滚 ③ 数据库不支持事务

事务传播机制记忆口诀:
REQUIRED(默认加入)、REQUIRES_NEW(独立新开)是核心!
事务失效五板斧:非public、内部调、吞异常、checked、MyISAM

2.5 常用注解速查表 ⭐⭐⭐⭐

📦 声明 Bean

@Component @Service @Repository @Controller @RestController @Configuration @Bean

@Component 是通用注解;@Service/@Repository/@Controller 是语义化的特化注解,功能相同

💉 依赖注入

@Autowired @Resource @Qualifier @Value

⚙️ 配置相关

@Configuration @ComponentScan @PropertySource @ConfigurationProperties @EnableAutoConfiguration

🌐 Web 控制器

@RequestMapping @GetMapping @PostMapping @PutMapping @DeleteMapping @RequestBody @ResponseBody @PathVariable @RequestParam @RequestHeader

🔀 条件装配

@ConditionalOnClass @ConditionalOnBean @ConditionalOnMissingBean @ConditionalOnProperty @ConditionalOnWebApplication @Profile

🔄 事务 & AOP

@Transactional @Aspect @Pointcut @Before @After @AfterReturning @AfterThrowing @Around

@Component vs @Bean 区别

对比@Component@Bean
使用位置 类上 @Configuration 类的方法上
Bean 名称 默认类名首字母小写 默认方法名,可自定义
适用范围 自己写的类 第三方库的类(你无法修改源码加@Component)
灵活性 简单,直接标记 更灵活,可以加逻辑判断

@ConfigurationProperties vs @Value

对比@ConfigurationProperties@Value
功能 批量绑定配置属性到 Bean 逐个注入配置值
前缀匹配 ✅ prefix="spring.datasource" ❌ 每个 @Value("${xxx}") 单独写
类型安全 ✅ 自动类型转换 ⚠️ 需要确保类型匹配
松散绑定 ✅ user-name → userName ❌ 必须精确匹配
SpEL ❌ 不支持 ✅ @Value("#{systemProperties.xxx}")
适用场景 一组相关配置(数据源、缓存等) 单个配置值注入

2.6 面试高频问答 Top 20 ⭐⭐⭐⭐⭐

1 Spring IoC 和 AOP 是什么? ⭐⭐⭐⭐⭐

IoC(控制反转):对象的创建和管理权从程序代码转移到 Spring 容器。以前自己 new 对象,现在由容器统一管理。

AOP(面向切面编程):将横切关注点(日志、权限、事务)从业务代码中抽离,通过动态代理统一织入。不修改业务代码就能增强功能。

IoC 是 Spring 的基础,AOP 是 Spring 的利器。IoC 管对象,AOP 管增强。

2 Bean 的生命周期? ⭐⭐⭐⭐⭐

核心流程:实例化 → 属性注入(DI) → Aware回调 → BeanPostProcessor前置 → 初始化(@PostConstruct → afterPropertiesSet → init-method) → BeanPostProcessor后置(AOP代理在此生成) → 就绪 → 销毁(@PreDestroy → destroy → destroy-method)

面试关键点:AOP 代理是在 BeanPostProcessor 的后置处理中生成的,不是初始化阶段!

3 Spring 怎么解决循环依赖? ⭐⭐⭐⭐⭐

通过 三级缓存 解决:

一级 singletonObjects:完整的 Bean

二级 earlySingletonObjects:早期引用(半成品)

三级 singletonFactories:对象工厂(需要时才生成早期引用)

核心思路:先实例化半成品对象放入缓存,让循环依赖能拿到引用,后续再完成初始化。

注意:构造器注入的循环依赖无法解决(因为连半成品都 new 不出来)。

4 JDK 动态代理和 CGLIB 的区别? ⭐⭐⭐⭐⭐

JDK 动态代理:基于接口,目标类必须实现接口。使用 Proxy.newProxyInstance()。生成快,调用稍慢。

CGLIB:基于继承,生成目标类的子类。不能代理 final 类。生成慢,调用快(FastClass 机制)。

Spring Boot 2.x 默认全部使用 CGLIB(proxy-target-class=true)

5 Spring Boot 自动配置原理? ⭐⭐⭐⭐⭐

核心流程

1. @SpringBootApplication 包含 @EnableAutoConfiguration

2. 通过 @Import(AutoConfigurationImportSelector) 导入选择器

3. SpringFactoriesLoader 读取 META-INF/spring.factories 中所有 AutoConfiguration 类

4. 通过 @ConditionalOnXxx 条件过滤,满足条件的才注册 Bean

5. 所以引入 starter 后自动生效 — 约定大于配置

6 @Autowired 和 @Resource 的区别? ⭐⭐⭐⭐

@Autowired:Spring 提供,先 byType 再 byName,配合 @Qualifier 指定名称

@Resource:JSR-250 标准(Java),先 byName 再 byType,通过 name 属性指定

• @Autowired 支持 required=false,@Resource 不支持

• 推荐优先使用 @Autowired(Spring 生态更好集成)

7 @Transactional 失效的场景? ⭐⭐⭐⭐⭐

5 大失效场景

1. 方法不是 public(AOP 只对 public 方法生效)

2. 同一个类内部调用 this.method()(不走代理,AOP 不生效)

3. 异常被 try-catch 吞了没抛出

4. 默认只回滚 RuntimeException,checked 异常(如 IOException)不回滚

5. 数据库引擎不支持事务(如 MyISAM)

最佳实践:@Transactional(rollbackFor = Exception.class) 加在 public 方法上

8 事务传播机制有哪些? ⭐⭐⭐⭐

7 种传播机制,最常考两种:

REQUIRED(默认):有事务就加入,没有就新建

REQUIRES_NEW:总是新建独立事务,挂起当前的(互不影响)

其他:SUPPORTS / MANDATORY / NOT_SUPPORTED / NEVER / NESTED

9 SpringMVC 的请求处理流程? ⭐⭐⭐⭐

HTTP请求 → DispatcherServlet(前端控制器) → HandlerMapping(根据URL找Controller方法) → HandlerAdapter(适配调用) → Controller(执行业务) → ViewResolver/@ResponseBody(视图解析/返回JSON) → HTTP响应

核心:DispatcherServlet 是总调度,所有请求都经过它。

10 Spring Boot 的启动流程? ⭐⭐⭐⭐

main() → SpringApplication.run() → 创建 SpringApplication → 加载 Initializer/Listener → 准备环境(读配置) → printBanner → createApplicationContext → refreshContext(核心!) → afterRefresh → callRunners → 启动完成

refresh() 中最关键的两步:

• invokeBeanFactoryPostProcessors:扫描注册 BeanDefinition + 自动配置条件过滤

• finishBeanFactoryInitialization:实例化所有单例 Bean(最耗时)

11 ApplicationContext 和 BeanFactory 的区别? ⭐⭐⭐

• BeanFactory 是最基础的 IoC 容器接口,懒加载,getBean 时才创建对象

• ApplicationContext 是 BeanFactory 的超集,预加载,启动时就创建所有单例 Bean

• ApplicationContext 额外支持:国际化(MessageSource)、事件发布(ApplicationEvent)、AOP自动集成、资源访问(ResourceLoader)

日常开发中 99% 使用 ApplicationContext

12 @Component 和 @Bean 的区别? ⭐⭐⭐

@Component:标注在类上,通过 @ComponentScan 扫描发现。适合自己写的类。

@Bean:标注在 @Configuration 类的方法上,方法返回值就是 Bean。适合第三方库的类(你无法修改源码加 @Component)。

• @Bean 更灵活,可以在方法里写初始化逻辑。

13 Spring 中的设计模式? ⭐⭐⭐⭐

工厂模式:BeanFactory / ApplicationContext 创建和管理 Bean

单例模式:Bean 默认 scope=singleton

代理模式:AOP 动态代理(JDK / CGLIB)

模板方法:JdbcTemplate / RestTemplate 定义算法骨架

观察者模式:ApplicationEvent + ApplicationListener 事件机制

适配器模式:HandlerAdapter 统一不同 Handler 调用方式

策略模式:Resource 接口的不同实现

14 Filter 和 Interceptor 的区别? ⭐⭐⭐

Filter:Servlet 规范,在 DispatcherServlet 之前执行,拦截所有请求(含静态资源)

Interceptor:Spring MVC 规范,在 DispatcherServlet 之后、Controller 之前执行,只拦截 Controller 方法

• Interceptor 能获取 Handler 信息,能直接注入 Spring Bean;Filter 不方便

• 执行顺序:Filter → DispatcherServlet → Interceptor → Controller

15 Spring Boot 如何自定义 Starter? ⭐⭐⭐

4 步走:

1. 创建 XxxAutoConfiguration 配置类(加 @Configuration + @ConditionalOnXxx)

2. 创建 XxxProperties 类(加 @ConfigurationProperties)绑定配置

3. 在 META-INF/spring.factories 中注册 AutoConfiguration 类

4. 创建 spring-boot-starter-xxx 模块(只做依赖聚合,没有代码)

16 @ConfigurationProperties 和 @Value 的区别? ⭐⭐

@ConfigurationProperties:批量绑定一组配置到 Bean,支持前缀匹配、松散绑定、类型安全。适合数据源、缓存等一组配置。

@Value:逐个注入配置值,支持 SpEL 表达式。适合单个简单值。

17 什么是 Spring 事件机制? ⭐⭐

Spring 事件机制基于观察者模式

ApplicationEvent:定义事件

ApplicationEventPublisher:发布事件(publishEvent())

@EventListener / ApplicationListener:监听并处理事件

• 典型用途:业务解耦(如下单后异步发送通知、更新统计等)

18 RestTemplate 和 WebClient / Feign 的区别? ⭐⭐

RestTemplate:同步 HTTP 客户端,Spring 提供。简单易用但已进入维护模式。

WebClient:异步非阻塞 HTTP 客户端,基于 WebFlux。适合高并发场景。

Feign:声明式 HTTP 客户端(Spring Cloud),通过接口+注解定义调用,集成了负载均衡。

新项目推荐 WebClient 或 Feign,RestTemplate 虽然还能用但已不推荐新功能使用。

19 Spring Boot 配置文件的加载顺序? ⭐⭐⭐

优先级从高到低(高优先级覆盖低优先级):

1. 命令行参数(--server.port=8081)

2. JNDI 属性

3. Java 系统属性(System.getProperties())

4. 操作系统环境变量

5. application-{profile}.properties/yml(外部 config 目录)

6. application-{profile}.properties/yml(classpath)

7. application.properties/yml(外部 config 目录)

8. application.properties/yml(classpath 根目录)

记忆:外部 > 内部,profile > 默认,命令行最优先

20 Spring 和 Spring Boot 的区别? ⭐⭐⭐

Spring:企业级 Java 框架,提供 IoC、AOP、事务管理等基础能力。配置较多(XML/Java Config)。

Spring Boot:基于 Spring 的快速开发框架。约定大于配置,自动配置、内嵌服务器、Starter 依赖聚合、Actuator 监控。

一句话总结:Spring Boot 不是替代 Spring,而是让 Spring 用起来更简单

对比SpringSpring Boot
配置方式大量XML/Java Config自动配置 + application.yml
服务器需要外置 Tomcat内嵌 Tomcat/Jetty/Undertow
依赖管理手动管理版本Starter 自动管理版本
启动方式部署 WAR 到服务器java -jar 一键启动

面试 Checklist — 上考场前的自检清单

✅ Part 1: Spring Core 必会(6 题)

#考点关键回答要点掌握度
1 IoC / DI 是什么 控制反转 + 依赖注入,以前自己 new,现在容器管理。解耦、方便测试
2 @Autowired vs @Resource Autowired 先类型再名称;Resource 先名称再类型
3 Bean 生命周期 实例化→注入→Aware→前置→初始化→后置(AOP代理)→使用→销毁
4 三级缓存解决循环依赖 一级完整Bean、二级早期引用、三级工厂;构造器注入无法解决
5 AOP 原理 动态代理;JDK(接口) vs CGLIB(继承);Spring Boot 默认 CGLIB
6 Spring 设计模式 工厂、单例、代理、模板方法、观察者、适配器、策略

✅ Part 2: Spring Boot 必会(7 题)

#考点关键回答要点掌握度
7 自动配置原理 @EnableAutoConfiguration → spring.factories → @Conditional 过滤 → 约定大于配置
8 启动流程 main → run → 准备环境 → createContext → refreshContext(核心) → callRunners
9 SpringMVC 请求流程 请求 → DispatcherServlet → HandlerMapping → HandlerAdapter → Controller → 响应
10 事务传播机制 REQUIRED(默认加入) vs REQUIRES_NEW(独立新开)
11 事务失效 5 大场景 非public、内部调用、吞异常、checked异常、MyISAM
12 Filter vs Interceptor Filter(Servlet规范, 前置) vs Interceptor(Spring MVC, 后置, 能注入Bean)
13 自定义 Starter AutoConfiguration + Properties + spring.factories + Starter模块

✅ Part 3: 加分项(了解即可)

#考点说明
14 @Component vs @Bean @Component 标自己写的类;@Bean 配第三方类
15 ApplicationContext vs BeanFactory 后者懒加载基础接口;前者预加载+国际化+事件+AOP
16 Spring 事件机制 ApplicationEvent + @EventListener,观察者模式实现业务解耦
17 配置文件加载顺序 命令行 > 外部 > classpath, profile > 默认
18 Spring vs Spring Boot Boot 不是替代 Spring,是让 Spring 更好用(自动配置+内嵌服务器+Starter)

面试前 30 分钟快速回顾重点:
① IoC/DI 思想 + @Autowired vs @Resource
② Bean 生命周期 8 步(记住 AOP 在后置处理!)
③ 三级缓存(一级完整、二级半成品、三级工厂)
④ JDK vs CGLIB(接口 vs 继承,Boot 默认 CGLIB)
⑤ 自动配置流程(注解→factories→Conditional→注册)
⑥ 事务失效五板斧(非public、内部调、吞异常、checked、MyISAM)
⑦ REQUIRED vs REQUIRES_NEW(加入 vs 独立新开)

面试回答技巧:先说结论(一句话),再展开细节,最后举一反三。
例如:被问"Bean 生命周期?" → 先说"8 个阶段" → 展开每一步 → 最后强调"AOP 在第 6 步后置处理中生成"这个关键点。

核心三原则速记

Spring三原则:① Bean默认单例,不要在Bean中存请求级状态 → ② @Transactional不能在类内部调用中生效 → ③ 循环依赖能用但不推荐