Spring 源码主线:IoC、AOP 与事务
沿着容器启动、Bean 生命周期、代理创建和事务拦截四条主线理解 Spring 源码。
为什么要沿主线读 Spring 源码
Spring 源码类很多,但核心问题只有三个:
- 配置如何变成 BeanDefinition;
- Bean 如何被实例化、注入、初始化并增强;
- 一次方法调用如何经过 AOP 和事务拦截器。
先理解调用链,再记扩展点和类名,比从某个实现类逐行背源码有效得多。
一、IoC 容器启动主线
ApplicationContext 启动的核心入口是 refresh()。可以把它理解成一次容器重建:准备环境、创建 BeanFactory、注册扩展点、实例化单例 Bean,最后发布启动完成事件。
准备环境
↓
创建并配置 BeanFactory
↓
加载 BeanDefinition
↓
执行 BeanFactoryPostProcessor
↓
注册 BeanPostProcessor
↓
初始化事件、国际化等基础设施
↓
实例化剩余非懒加载单例 Bean
↓
发布 ContextRefreshedEvent
BeanDefinition 是什么
BeanDefinition 是 Bean 的“配方”,而不是 Bean 实例。它保存:
- Bean 类型;
- scope、lazy、primary 等配置;
- 构造参数和属性依赖;
- 初始化与销毁方法;
- 工厂方法等元数据。
XML、注解和 Java Config 最终都要转成 BeanDefinition,注册到 BeanFactory 中。
两类重要后置处理器
BeanFactoryPostProcessor 处理的是 BeanDefinition,可以在 Bean 实例化前修改元数据。典型实现包括配置类解析器和属性占位符处理器。
BeanPostProcessor 处理的是 Bean 实例,可以在初始化前后修改或包装 Bean。AOP 自动代理就是在这条扩展链上完成的。
一句话区分:
BeanFactoryPostProcessor 改“配方”,BeanPostProcessor 改“成品”。
二、Bean 创建与生命周期
单例 Bean 会依次经历实例化、依赖注入、初始化增强、对外提供服务和销毁。完整流程如下:
需要注意,“实例化”只是创建对象,“初始化”还包括依赖注入、生命周期回调和代理增强。
常见生命周期回调顺序
常见顺序是:
- 构造对象;
- 注入属性;
- 执行
BeanNameAware、BeanFactoryAware等回调; - 执行初始化前的 BeanPostProcessor,其中
CommonAnnotationBeanPostProcessor会触发@PostConstruct; - 执行
InitializingBean.afterPropertiesSet(); - 执行自定义
init-method; - 执行初始化后的 BeanPostProcessor,AOP 代理常在这里生成。
三、三级缓存与循环依赖
Spring 单例创建相关的三个缓存通常是:
singletonObjects:完整初始化后的单例
earlySingletonObjects:已提前暴露的早期引用
singletonFactories:能够生成早期引用的 ObjectFactory
以 A、B 通过字段或 setter 相互依赖为例:
三级缓存的关键价值不只是“提前拿到对象”,还在于能够通过工厂生成早期代理引用,尽量保证依赖方拿到的对象与最终代理对象一致。
解决边界
Spring 不能解决所有循环依赖:
- 构造器循环依赖通常无法解决,因为对象还没实例化就已经需要对方;
- prototype Bean 不使用单例缓存,因此不能依赖这套机制;
- 部分代理、异步初始化或自定义后置处理器可能让早期引用与最终对象不一致;
- 最好的方案通常仍是重新划分职责,而不是依赖循环引用。
四、AOP 代理是怎样产生的
Spring AOP 的主线是:找到 Advisor,判断 Bean 是否命中切点,然后用自动代理创建器包装 Bean。
Bean 初始化
↓
AbstractAutoProxyCreator 后置处理
↓
查找匹配的 Advisor
↓
创建 JDK 动态代理或 CGLIB 代理
↓
调用代理方法
↓
执行 MethodInterceptor 链
↓
调用目标方法
JDK 动态代理与 CGLIB
- JDK 动态代理基于接口生成代理;
- CGLIB 通过生成目标类的子类实现代理;
final类不能被继承,final方法不能被覆盖,因此不能按普通方式被 CGLIB 增强;- 业务代码应依赖抽象,但代理方式不应成为架构设计的核心目标。
为什么自调用会导致切面失效
public void outer() {
inner();
}
@Transactional
public void inner() {
}
外部调用通常是:
调用方 → 代理对象 → 拦截器 → 目标对象
而 outer() 内部的 inner() 是目标对象上的 this.inner(),没有再次经过代理,因此事务、缓存、异步等基于代理的增强可能不生效。
更稳妥的做法是拆分 Bean,让调用跨越代理边界;不要把“从容器中获取自己”作为默认方案。
五、声明式事务源码主线
声明式事务本质上是一个 AOP Advisor。方法调用进入 TransactionInterceptor 后,根据事务属性决定开启、加入或挂起事务,再调用目标方法,最后提交或回滚。
调用代理方法
↓
TransactionInterceptor
↓
读取 @Transactional 属性
↓
PlatformTransactionManager 获取事务
↓
执行目标方法
├─ 正常:提交
└─ 异常:按回滚规则回滚
传播行为重点
REQUIRED:有事务就加入,没有就新建,默认值;REQUIRES_NEW:挂起外层事务,创建独立事务;NESTED:通常基于保存点,仍属于同一个物理事务;SUPPORTS:有事务就加入,没有则非事务执行;NOT_SUPPORTED:挂起现有事务,非事务执行。
REQUIRES_NEW 与 NESTED 不等价:前者是独立事务,内层提交后不随外层回滚;后者一般共享外层物理事务,只能回滚到保存点。
默认回滚规则
Spring 默认对 RuntimeException 和 Error 回滚,对受检异常默认不回滚。需要时显式配置:
@Transactional(rollbackFor = Exception.class)
不要为了触发回滚而吞掉异常。如果 catch 后既不重新抛出,也不手动标记回滚,事务拦截器会把方法视为正常完成。
常见失效场景
- 同类自调用绕过代理;
- Bean 没有被 Spring 容器管理;
- 方法无法被当前代理方式增强;
- 异常被捕获后未继续抛出;
- 数据库表或连接不支持事务;
- 新线程、异步任务不继承当前线程的事务上下文;
- 事务范围过大,在事务中执行远程调用或长时间计算。
六、源码阅读路线
推荐依次跟踪:
AbstractApplicationContext.refresh():容器启动总流程;DefaultListableBeanFactory:BeanDefinition 注册与依赖查找;AbstractAutowireCapableBeanFactory.doCreateBean():Bean 创建;DefaultSingletonBeanRegistry:单例缓存;AbstractAutoProxyCreator:自动代理;JdkDynamicAopProxy/ CGLIB 拦截器:方法调用;TransactionInterceptor:事务边界。
七、面试回答主线
被问“Spring 如何创建一个带事务的 Bean”时,可以这样组织:
容器先把配置解析为 BeanDefinition,通过 BeanFactory 创建 Bean,完成实例化、依赖注入和初始化回调。BeanPostProcessor 会在初始化前后介入,事务基础设施中的自动代理创建器判断该 Bean 是否命中事务 Advisor,命中后返回代理对象。外部调用代理方法时进入 TransactionInterceptor,由事务管理器创建或加入事务,再执行目标方法,并按异常和回滚规则提交或回滚。
评论与讨论
回复 :
留下你的想法