本文从面试视角梳理 Spring Boot 启动流程、自动配置、IOC/DI、Bean 生命周期、Spring MVC、AOP、动态代理、
@Transactional以及事务失效场景。重点不是孤立地背概念,而是理解这些机制如何串成一条完整调用链。
摘要
Spring Boot 面试题看起来很多,但底层主线并不复杂:
SpringApplication.run()启动应用并刷新 Spring 容器;- 自动配置和组件扫描注册 BeanDefinition;
- IOC 容器创建 Bean,通过 DI 完成对象组装;
- Bean 初始化过程中,后置处理器可能为对象创建 AOP 代理;
- Web 请求由 Spring MVC 分发到 Controller;
- Controller 调用 Service 代理,事务拦截器在业务方法前后开启、提交或回滚事务。
只要抓住"容器、Bean、代理、调用边界"这几个关键词,大部分 Spring 面试追问都能顺着原理回答。
关键词: Spring Boot、IOC、DI、Bean 生命周期、Spring MVC、AOP、动态代理、事务、面试

图片说明:一二和布布带我们沿着
启动 → 自动配置 → IOC/DI → Bean 生命周期 → MVC → AOP/动态代理 → 事务的顺序,串起 Spring Boot 的核心面试知识。
一、Spring Boot 启动流程
1. 面试时先给结论
SpringApplication.run() 的主要工作可以概括为四步:
- 准备运行环境
Environment; - 创建合适的
ApplicationContext; - 调用
refresh()刷新容器,完成配置解析、Bean 创建和 Web Server 启动; - 执行 Runner,发布应用就绪事件。
面试官真正想听到的关键词,通常是:
text
SpringApplication.run()
↓
准备 Environment
↓
创建 ApplicationContext
↓
refresh() 刷新容器
↓
解析配置、注册并创建 Bean
↓
启动内嵌 Web Server
↓
执行 Runner,应用就绪
2. 启动入口
java
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
表面上只调用了一行代码,背后主要经历以下阶段。
3. 创建 SpringApplication
Spring Boot 会记录主配置类,并推断当前应用类型:
- Servlet Web 应用;
- Reactive Web 应用;
- 非 Web 应用。
同时,还会准备启动过程中需要使用的初始化器和监听器。
4. 准备 Environment
Boot 会加载并整理配置来源,例如:
- 命令行参数;
- JVM 系统属性;
- 操作系统环境变量;
application.properties;application.yml;- Profile 对应的配置文件。
这些配置最终被组合到 Environment 中,后续的属性绑定和条件装配都会依赖它。
5. 创建 ApplicationContext
Spring Boot 会根据应用类型创建对应的上下文,然后完成以下工作:
- 关联
Environment; - 执行
ApplicationContextInitializer; - 注册主配置类;
- 准备进入容器刷新阶段。
6. 核心:refresh()
Spring Boot 启动的核心,最终会落到 Spring Framework 的:
java
AbstractApplicationContext#refresh
这个方法负责真正刷新 IOC 容器。面试时不必背完整源码,但要知道几个重要阶段:
- 准备 BeanFactory;
- 执行
BeanFactoryPostProcessor; - 解析配置类、组件扫描、
@Import和自动配置; - 注册
BeanPostProcessor; - 初始化事件广播器等基础组件;
- 创建非懒加载单例 Bean;
- 启动内嵌 Web Server;
- 完成刷新并发布事件。
其中,ConfigurationClassPostProcessor 非常关键,它负责处理:
@Configuration;@ComponentScan;@Import;@Bean。
自动配置类也是通过配置解析流程进入 Spring 容器的。
7. 应用启动完成
容器刷新完成后,Spring Boot 会执行:
ApplicationRunner;CommandLineRunner。
然后发布 ApplicationReadyEvent,表示应用已经可以对外提供服务。如果启动失败,则会发布 ApplicationFailedEvent。
8. 高频追问:两种后置处理器有什么区别
BeanFactoryPostProcessor
处理的是 BeanDefinition,也就是 Bean 的"创建说明书"。执行时,大部分普通 Bean 还没有实例化。
BeanPostProcessor
处理的是已经实例化的 Bean 对象,可以在 Bean 初始化前后执行增强,甚至返回另一个对象。Spring AOP 创建代理就是典型应用。
一句话记忆:
BeanFactoryPostProcessor修改 Bean 定义,BeanPostProcessor处理 Bean 实例。
二、自动配置原理
1. @SpringBootApplication 包含什么
@SpringBootApplication 主要组合了三个注解:
java
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
它们分别负责:
| 注解 | 作用 |
|---|---|
@SpringBootConfiguration |
标识 Spring Boot 主配置类 |
@EnableAutoConfiguration |
开启自动配置 |
@ComponentScan |
扫描并注册业务组件 |
因为 @ComponentScan 默认从启动类所在包向下扫描,所以启动类通常应该放在项目根包。
2. 自动配置类从哪里加载
@EnableAutoConfiguration 通过 @Import 机制导入自动配置选择器。
现代 Spring Boot 主要从依赖包中的以下文件读取自动配置候选类:
text
META-INF/spring/
org.springframework.boot.autoconfigure.AutoConfiguration.imports
旧版本面经中经常提到:
text
META-INF/spring.factories
这是历史版本的重要实现,但面试现代 Spring Boot 时,只回答 spring.factories 已经不够完整。
3. 为什么引入 starter 就能使用功能
严格来说,starter 通常只是一个依赖聚合器。真正注册默认 Bean 的,是对应的 auto-configure 模块。
自动配置类通常类似下面这样:
java
@AutoConfiguration
@ConditionalOnClass(DataSource.class)
public class MyDataSourceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public DataSource dataSource() {
return new DefaultDataSource();
}
}
它表达的含义是:
- classpath 中存在
DataSource; - 用户没有自己注册
DataSource; - Spring Boot 才提供默认实现。
4. 常见条件注解
| 注解 | 生效条件 |
|---|---|
@ConditionalOnClass |
classpath 中存在指定类 |
@ConditionalOnMissingClass |
classpath 中不存在指定类 |
@ConditionalOnBean |
容器中存在指定 Bean |
@ConditionalOnMissingBean |
容器中不存在指定 Bean |
@ConditionalOnProperty |
配置属性满足要求 |
@ConditionalOnWebApplication |
当前属于 Web 应用 |
自动配置最重要的设计原则是:
提供合理默认值,但优先尊重用户配置。
例如,自动配置方法带有 @ConditionalOnMissingBean 时,只要用户自己定义了同类型 Bean,默认配置就会退让。
5. 自动配置没有生效怎么排查
可以按照以下顺序检查:
- 使用
--debug或配置debug=true查看条件评估报告; - 检查目标类是否真的在 classpath;
- 检查配置属性是否满足条件;
- 检查容器中是否已经存在同类型 Bean;
- 检查是否通过注解或配置排除了自动配置类;
- 使用 Actuator 的
conditions端点进一步分析。
三、IOC 与 DI
1. IOC 是思想,DI 是实现方式
IOC,Inversion of Control,中文通常叫控制反转。
它指的是:对象的创建、组装和生命周期管理,不再由业务代码主动完成,而是交给 Spring 容器。
DI,Dependency Injection,中文叫依赖注入。
它是 IOC 最常见的实现方式:Spring 创建对象后,把对象需要的依赖注入进去。
传统写法:
java
public class OrderService {
private final OrderRepository repository =
new JdbcOrderRepository();
}
使用依赖注入:
java
@Service
public class OrderService {
private final OrderRepository repository;
public OrderService(OrderRepository repository) {
this.repository = repository;
}
}
前者由 OrderService 主动创建依赖,后者只声明自己需要什么,由容器负责提供。
2. 为什么推荐构造器注入
Spring 支持三种常见注入方式:
- 构造器注入;
- Setter 注入;
- 字段注入。
业务代码优先推荐构造器注入,因为它:
- 能明确表达必需依赖;
- 可以将字段声明为
final; - 对象创建完成后就是可用状态;
- 单元测试时可以直接构造对象;
- 更容易暴露循环依赖;
- 构造参数过多时,会提醒我们类的职责可能过重。
如果类只有一个构造器,通常不必添加 @Autowired。
3. 一个接口有多个实现怎么办
例如:
java
public interface PayStrategy {
void pay();
}
存在多个实现时,可以使用:
@Primary:指定默认实现;@Qualifier:指定具体候选 Bean;List<PayStrategy>:注入所有实现;Map<String, PayStrategy>:按 Bean 名称获取所有实现。
后两种方式特别适合策略模式。
4. 循环依赖怎么回答
首先要明确:循环依赖通常是设计问题,优先考虑拆分职责。
在允许循环引用的前提下,Spring 可以借助三级缓存和提前暴露引用,解决一部分基于字段或 Setter 注入的单例循环依赖。
但构造器循环依赖通常无法解决:
text
创建 A 需要 B
创建 B 又需要 A
此时 A 和 B 都无法先完成实例化。
所谓三级缓存,主要保存:
- 已经完整初始化的单例;
- 提前暴露的单例对象;
- 可以创建早期引用的对象工厂。
第三层工厂的重要意义之一,是让 Spring 有机会提前暴露代理引用,减少其他 Bean 拿到裸对象而最终容器中保存代理对象的不一致问题。
不要把三级缓存回答成:
Spring 可以解决所有循环依赖。
这是错误的。
四、Bean 生命周期
1. 标准生命周期
以普通 singleton Bean 为例:
text
注册 BeanDefinition
↓
实例化 Bean
↓
属性填充、依赖注入
↓
Aware 回调
↓
BeanPostProcessor 初始化前处理
↓
执行初始化方法
↓
BeanPostProcessor 初始化后处理
↓
Bean 可以使用
↓
容器关闭时执行销毁方法
展开回答如下:
- Spring 根据 BeanDefinition 实例化对象;
- 完成属性填充和依赖注入;
- 调用
BeanNameAware、BeanFactoryAware等 Aware 回调; - 执行
postProcessBeforeInitialization(); - 执行 Bean 初始化回调;
- 执行
postProcessAfterInitialization(); - 如果匹配 AOP 规则,这里可能返回代理对象;
- 容器关闭时执行销毁回调。
2. 初始化方法的顺序
常见顺序为:
text
@PostConstruct
↓
InitializingBean.afterPropertiesSet()
↓
自定义 init-method
销毁方法的常见顺序为:
text
@PreDestroy
↓
DisposableBean.destroy()
↓
自定义 destroy-method
业务代码通常优先使用 @PostConstruct、@PreDestroy 或自定义方法,避免直接耦合 Spring 生命周期接口。
3. AOP 代理在哪一步产生
自动代理创建器属于 BeanPostProcessor。
它会判断当前 Bean 是否匹配某个 Advisor。如果匹配,就会包装目标对象并返回代理对象。其他 Bean 最终注入到的,可能不是原始对象,而是代理对象。
这也解释了为什么不应该在 @PostConstruct 中依赖 @Transactional:Bean 此时仍处于初始化过程,不能假设完整代理已经成为外部调用入口。
4. prototype Bean 会自动销毁吗
Spring 负责 prototype Bean 的创建和初始化,但把它交给调用者后,通常不会继续跟踪其完整生命周期。
因此,prototype Bean 不会像 singleton Bean 一样,在容器关闭时自动执行完整销毁流程。资源释放通常需要调用方自己负责。
五、Spring MVC 请求流程
1. 核心组件:DispatcherServlet
Spring MVC 基于 Servlet API,使用前端控制器模式。所有匹配的请求统一进入 DispatcherServlet,再由它协调其他组件处理。
一条典型请求链路如下:
text
客户端请求
↓
Filter Chain
↓
DispatcherServlet
↓
HandlerMapping 查找处理器
↓
HandlerAdapter 调用 Controller
↓
Controller 调用 Service
↓
返回值处理 / 异常处理
↓
JSON 响应或视图渲染
2. 详细处理过程
- 请求首先经过 Servlet Filter;
DispatcherServlet接收请求;HandlerMapping查找 Handler 和对应拦截器;- 返回
HandlerExecutionChain; DispatcherServlet选择对应的HandlerAdapter;- 参数解析器把请求参数转换成 Controller 方法参数;
HandlerAdapter调用 Controller;- 返回值处理器处理 Controller 返回值;
HttpMessageConverter将对象序列化为 JSON,或者由ViewResolver解析页面;- 异常交给
HandlerExceptionResolver体系处理。
3. HandlerMapping 和 HandlerAdapter 为什么分开
HandlerMapping负责找到谁处理请求;HandlerAdapter负责使用什么方式调用它。
不同类型的 Handler 可能拥有不同调用方式。通过适配器模式,DispatcherServlet 不需要了解所有 Handler 的具体实现。
4. Filter、Interceptor、AOP 的区别
| 机制 | 工作层次 | 典型处理范围 | 常见用途 |
|---|---|---|---|
| Filter | Servlet 容器层 | 匹配的 HTTP 请求 | 编码、CORS、安全、请求包装 |
| Interceptor | Spring MVC 层 | Handler 调用前后 | 登录校验、接口审计、MVC 上下文 |
| AOP | Spring Bean 方法层 | 匹配切点的方法 | 事务、日志、权限、重试、指标 |
六、AOP 与动态代理
1. AOP 解决什么问题
事务、日志、鉴权、监控等逻辑往往分散在许多业务模块中。
如果每个方法都手动编写这些代码,会产生大量重复。AOP 将横跨多个模块的逻辑提取成切面,再统一织入目标方法。
2. AOP 核心术语
| 术语 | 含义 |
|---|---|
| Aspect | 切面,切点和增强逻辑的组合 |
| Join Point | 连接点,Spring AOP 中主要指方法执行 |
| Pointcut | 切点,决定增强哪些方法 |
| Advice | 增强逻辑 |
| Target | 原始目标对象 |
| Proxy | 包装目标对象的代理对象 |
| Advisor | Pointcut 与 Advice 的组合 |
Advice 常见类型包括:
- Before;
- After Returning;
- After Throwing;
- After;
- Around。
3. @Around 示例
java
@Aspect
@Component
public class TimingAspect {
@Around("execution(* com.example.order..*(..))")
public Object timing(ProceedingJoinPoint point) throws Throwable {
long start = System.nanoTime();
try {
return point.proceed();
} finally {
long cost = System.nanoTime() - start;
System.out.println("cost=" + cost);
}
}
}
多个切面匹配同一个方法时,会形成拦截器链。调用过程类似一层层包裹的洋葱。
4. JDK 动态代理
JDK 动态代理基于接口,核心是 InvocationHandler:
java
OrderService proxy = (OrderService) Proxy.newProxyInstance(
OrderService.class.getClassLoader(),
new Class<?>[]{OrderService.class},
(proxyObject, method, args) -> {
System.out.println("before");
try {
return method.invoke(target, args);
} finally {
System.out.println("after");
}
}
);
客户端调用的是代理对象。代理先执行增强逻辑,然后再调用目标对象。
5. CGLIB 类代理
CGLIB 会动态生成目标类的子类,通过重写可覆盖的方法完成拦截,因此目标类不一定需要实现接口。
但类代理存在限制:
final类不能被继承;final方法不能被重写;private方法不能被子类重写。
因此,这些位置无法使用常规 CGLIB 类代理完成增强。
6. Spring Framework 与 Spring Boot 的默认代理选择
Spring Framework 的基础代理规则通常描述为:
- 目标对象实现接口时,可以使用 JDK 动态代理;
- 没有接口时,使用 CGLIB 类代理。
Spring Boot 的 AOP 自动配置默认倾向使用 CGLIB 类代理。可以通过以下配置切换为 JDK 代理:
properties
spring.aop.proxy-target-class=false
面试时,应区分"Spring 支持哪两类代理"和"Spring Boot 默认使用哪一类代理"。
7. 为什么自调用无法被增强
text
调用者
↓
proxy.foo()
↓
Advice
↓
target.foo()
↓
this.bar()
调用已经进入 target.foo() 后,this 指向目标对象,不是代理对象。
因此,this.bar() 不会重新经过代理,bar() 上的事务、缓存、异步或自定义切面都可能无法触发。
七、@Transactional 原理
1. 注解本身只是事务元数据
@Transactional 不负责直接操作数据库事务,它只是告诉 Spring:这个方法需要什么样的事务语义。
真正执行事务逻辑的是:
- AOP 代理;
TransactionInterceptor;PlatformTransactionManager的具体实现。
典型执行过程如下:
text
调用事务代理
↓
读取 @Transactional 属性
↓
开启新事务或加入已有事务
↓
调用目标业务方法
↓
正常返回:提交
符合规则的异常:回滚
↓
释放并解绑事务资源
2. 默认事务行为
@Transactional 不写参数时,常见默认值包括:
- 传播行为:
REQUIRED; - 隔离级别:使用底层数据库或数据源默认值;
- 事务类型:读写事务;
- 超时时间:使用底层事务系统默认值;
RuntimeException和Error默认回滚;- 受检异常默认不回滚。
readOnly = true 通常是一种优化提示,不应该简单理解成所有数据库环境都会强制禁止写入。
3. 高频事务传播行为
REQUIRED
默认传播行为。有事务就加入,没有事务就创建。
如果内部事务逻辑把公共事务标记成 rollback-only,外层即使捕获异常并尝试提交,也可能收到 UnexpectedRollbackException。
REQUIRES_NEW
挂起外部事务,创建独立物理事务。内外事务可以分别提交或回滚。
它通常还会申请新的数据库连接。如果大量并发线程都持有外层事务连接,又等待内部事务获取新连接,可能耗尽连接池。
NESTED
通常基于同一个物理事务的 savepoint 实现局部回滚,主要适用于支持 JDBC 保存点的事务管理器。
它与 REQUIRES_NEW 不同:前者通常共享物理事务,后者使用独立物理事务。
其他传播行为
SUPPORTS:有事务就加入,没有也可以执行;NOT_SUPPORTED:以非事务方式运行,并挂起已有事务;MANDATORY:必须已经存在事务;NEVER:必须不存在事务。
八、事务失效的常见场景
事务失效问题看似很多,本质上主要检查两点:
- 调用有没有经过代理;
- 异常有没有满足回滚规则。
1. 同类方法内部调用
java
@Service
public class OrderService {
public void create() {
save();
}
@Transactional
public void save() {
// database operations
}
}
create() 内部调用 save(),本质是:
java
this.save();
调用没有经过事务代理,所以 save() 上的事务不会被拦截。
推荐的处理方式,是将事务方法拆分到另一个 Spring Bean 中,通过 Bean 间调用进入代理。
2. 对象不是 Spring Bean
java
OrderService service = new OrderService();
自己 new 出来的对象没有被 Spring 管理,也没有 AOP 代理,@Transactional 不会生效。
3. 方法无法被代理
更准确的回答是:
- JDK 代理只能代理接口暴露的方法;
- CGLIB 无法增强
final类和final方法; private方法无法被常规类代理重写;- Spring Framework 6 的类代理在特定情况下可以支持
protected或包可见事务方法; - 工程上仍建议将事务放在具体类的
public业务方法上。
"事务只能放在 public 方法上"是偏旧、偏保守的面经结论,不适合不加版本前提地绝对化表达。
4. 异常被业务代码吞掉
java
@Transactional
public void createOrder() {
try {
repository.save();
} catch (Exception e) {
log.warn("save failed", e);
}
}
异常没有抛出事务代理边界,事务拦截器看到的是正常返回,默认就会尝试提交。
5. 受检异常默认不回滚
例如方法抛出 IOException,默认可能不会回滚。需要根据业务要求配置:
java
@Transactional(rollbackFor = Exception.class)
但不建议不分场景地给所有方法都添加 rollbackFor = Exception.class,应结合业务异常体系设计回滚规则。
6. 跨线程调用
传统命令式事务通常通过 ThreadLocal 将事务资源绑定到当前线程。
如果业务执行过程中:
- 手动创建新线程;
- 使用线程池;
- 切换到
@Async方法;
原线程事务不会自动传播到新线程。异步任务应该在新线程入口重新定义事务边界。
7. 使用了错误的事务管理器
多数据源系统中,如果方法选择的 TransactionManager 与实际访问的数据源不匹配,就会表现出"事务没有生效"。
还需要检查:
- 所有操作是否使用同一个数据源;
- 是否确实加入同一个事务;
- 数据库和表的存储引擎是否支持事务。
8. 在初始化阶段调用事务方法
不要依赖 @PostConstruct 中的事务调用。此时 Bean 仍处于初始化阶段,事务代理不一定已经成为当前调用入口。
9. 把数据库事务误认为分布式事务
本地数据库事务不能回滚已经完成的:
- HTTP 调用;
- 消息发送;
- 文件写入;
- 其他系统中的数据修改。
跨系统一致性通常需要 outbox、可靠消息、幂等和补偿机制,而不是仅仅添加一个 @Transactional。
九、把所有知识点串成一道综合面试题
问题
一个带有 @Transactional 的 Service,是如何被 Controller 调用并最终提交事务的?
参考回答
应用从
SpringApplication.run()启动。Spring Boot 先准备 Environment,再创建 ApplicationContext,并在refresh()阶段解析主配置、组件扫描和自动配置,注册 BeanDefinition。随后,IOC 容器实例化 Controller、Service 和 Repository,通过 DI 完成对象组装。Service 初始化过程中,自动代理创建器发现它匹配事务 Advisor,于是为它创建 AOP 代理。Controller 最终注入到的通常是这个代理对象。
Web Server 启动后,请求先经过 Filter,再进入
DispatcherServlet。HandlerMapping找到 Controller,HandlerAdapter完成参数解析并调用 Controller。Controller 调用 Service 时,调用首先进入事务代理。
TransactionInterceptor读取@Transactional属性,通过事务管理器开启事务或加入已有事务,然后调用目标 Service。业务方法正常返回就提交,抛出符合回滚规则的异常就回滚。如果 Service 内部通过
this调用同类事务方法,调用不会再次经过代理,因此该方法上的事务增强可能失效。
这段回答把以下知识点全部串了起来:
- Spring Boot 启动;
- 自动配置;
- IOC/DI;
- Bean 生命周期;
- Spring MVC;
- AOP;
- 动态代理;
@Transactional;- 事务失效。
十、高频面试题速答
1. Spring Boot 和 Spring Framework 是什么关系
Spring Framework 提供 IOC、AOP、事务、MVC 等核心能力;Spring Boot 通过 starter、自动配置、内嵌服务器和约定优于配置,降低 Spring 应用的搭建成本。
Spring Boot 没有替代 Spring Framework,而是组织并自动配置它。
2. @Autowired 是如何生效的
Spring 注册负责处理注解的 BeanPostProcessor。Bean 创建过程中,处理器解析构造器、字段或方法上的注入点,再按照类型、@Primary、@Qualifier 等规则选择候选 Bean 并注入。
3. @Transactional 是 AOP 吗
声明式事务通常通过 Spring AOP 代理实现。注解只是事务元数据,真正开启、提交和回滚事务的是事务拦截器和事务管理器。
4. Bean 和普通 Java 对象有什么区别
Bean 本质上仍然是 Java 对象,但它的创建、配置、依赖注入、初始化、销毁和代理增强由 Spring 容器负责。
脱离容器自己 new 出来的同类对象,不会自动获得这些能力。
5. 为什么事务方法建议放在 Service 层
事务边界通常应该包围一个完整业务用例。Service 层负责组织多个 Repository 操作,更适合定义业务事务边界。
如果把事务零散地放在 Repository 层,多个数据库操作可能无法自然组成一个整体事务。
6. 事务没有回滚应该怎么排查
可以沿着四条线检查:
- 调用是否经过 Spring 代理;
- 异常是否抛出了代理边界;
- 异常是否符合回滚规则;
- 所有数据操作是否加入同一个事务。
十一、面试表达建议
回答 Spring 原理题时,可以采用以下结构:
text
一句话结论
↓
核心执行流程
↓
关键扩展点或源码入口
↓
容易失效的边界条件
例如回答事务原理:
- 先说事务基于 AOP 代理;
- 再说
TransactionInterceptor和事务管理器的执行流程; - 接着说默认传播行为与回滚规则;
- 最后补充自调用、异常吞掉和跨线程问题。
比起背很多类名,面试官更关心你能否解释:
- Bean 什么时候进入容器;
- 依赖什么时候注入;
- 代理什么时候生成;
- 调用有没有经过代理;
- 事务边界究竟在哪里。
十二、总结
本文的九个知识点可以浓缩为一句话:
Spring Boot 启动并刷新 Spring 容器,自动配置和组件扫描注册 BeanDefinition;IOC 容器创建并组装 Bean,BeanPostProcessor 为匹配的对象生成 AOP 代理;Spring MVC 把请求分发到 Controller,Controller 通过代理调用 Service,事务拦截器在业务方法前后完成事务管理。
最后再记住三个判断原则:
- IOC 的核心是对象控制权交给容器;
- AOP 的核心是调用经过代理和拦截器链;
- 事务排查的核心是代理边界、线程边界和异常边界。
理解这条主线之后,启动流程、自动配置、Bean 生命周期、MVC 和事务就不再是互不相关的面试题,而是一套完整的运行机制。