Spring

Spring 源码主线:IoC、AOP 与事务

沿着容器启动、Bean 生命周期、代理创建和事务拦截四条主线理解 Spring 源码。

难度:深入更新:2026-07-16

为什么要沿主线读 Spring 源码

Spring 源码类很多,但核心问题只有三个:

  1. 配置如何变成 BeanDefinition;
  2. Bean 如何被实例化、注入、初始化并增强;
  3. 一次方法调用如何经过 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 会依次经历实例化、依赖注入、初始化增强、对外提供服务和销毁。完整流程如下:

Spring Bean 生命周期:从 BeanDefinition 到销毁回调

需要注意,“实例化”只是创建对象,“初始化”还包括依赖注入、生命周期回调和代理增强。

常见生命周期回调顺序

常见顺序是:

  1. 构造对象;
  2. 注入属性;
  3. 执行 BeanNameAwareBeanFactoryAware 等回调;
  4. 执行初始化前的 BeanPostProcessor,其中 CommonAnnotationBeanPostProcessor 会触发 @PostConstruct
  5. 执行 InitializingBean.afterPropertiesSet()
  6. 执行自定义 init-method
  7. 执行初始化后的 BeanPostProcessor,AOP 代理常在这里生成。

三、三级缓存与循环依赖

Spring 单例创建相关的三个缓存通常是:

singletonObjects:完整初始化后的单例
earlySingletonObjects:已提前暴露的早期引用
singletonFactories:能够生成早期引用的 ObjectFactory

以 A、B 通过字段或 setter 相互依赖为例:

Spring 三级缓存解决单例 Bean 循环依赖流程

三级缓存的关键价值不只是“提前拿到对象”,还在于能够通过工厂生成早期代理引用,尽量保证依赖方拿到的对象与最终代理对象一致。

解决边界

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_NEWNESTED 不等价:前者是独立事务,内层提交后不随外层回滚;后者一般共享外层物理事务,只能回滚到保存点。

默认回滚规则

Spring 默认对 RuntimeExceptionError 回滚,对受检异常默认不回滚。需要时显式配置:

@Transactional(rollbackFor = Exception.class)

不要为了触发回滚而吞掉异常。如果 catch 后既不重新抛出,也不手动标记回滚,事务拦截器会把方法视为正常完成。

常见失效场景

  1. 同类自调用绕过代理;
  2. Bean 没有被 Spring 容器管理;
  3. 方法无法被当前代理方式增强;
  4. 异常被捕获后未继续抛出;
  5. 数据库表或连接不支持事务;
  6. 新线程、异步任务不继承当前线程的事务上下文;
  7. 事务范围过大,在事务中执行远程调用或长时间计算。

六、源码阅读路线

推荐依次跟踪:

  1. AbstractApplicationContext.refresh():容器启动总流程;
  2. DefaultListableBeanFactory:BeanDefinition 注册与依赖查找;
  3. AbstractAutowireCapableBeanFactory.doCreateBean():Bean 创建;
  4. DefaultSingletonBeanRegistry:单例缓存;
  5. AbstractAutoProxyCreator:自动代理;
  6. JdkDynamicAopProxy / CGLIB 拦截器:方法调用;
  7. TransactionInterceptor:事务边界。

七、面试回答主线

被问“Spring 如何创建一个带事务的 Bean”时,可以这样组织:

容器先把配置解析为 BeanDefinition,通过 BeanFactory 创建 Bean,完成实例化、依赖注入和初始化回调。BeanPostProcessor 会在初始化前后介入,事务基础设施中的自动代理创建器判断该 Bean 是否命中事务 Advisor,命中后返回代理对象。外部调用代理方法时进入 TransactionInterceptor,由事务管理器创建或加入事务,再执行目标方法,并按异常和回滚规则提交或回滚。

参考资料

DISCUSSION

评论与讨论

留下你的想法