Skip to content

Spring 源码剖析 ​

Java 高级 · 底层原理

多数工程师只停留在"会用注解",但 Spring 真正的魅力在 refresh() 这一条主线上:它把"对象的创建、依赖的注入、代理的织入、事务的开启"全部编排成可扩展的生命周期。本文按"容器启动 → 循环依赖 → AOP 自动代理 → 事务拦截器链"四步拆解源码脉络,让你读任何 Spring 衍生框架(Spring Boot、Spring Cloud)都有抓手。

一、refresh():容器启动的十二步 ​

AbstractApplicationContext.refresh() 是 IoC 容器的总导演。它不是一个方法,而是一连串"模板 + 扩展点":

java
public void refresh() {
    prepareRefresh();                      // 1. 环境准备、校验必填配置
    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 2. 拿到/刷新 BeanFactory,加载 BeanDefinition
    prepareBeanFactory(beanFactory);       // 3. 注册内置组件(SpacElResolver/ApplicationContextAwareProcessor)
    postProcessBeanFactory(beanFactory);   // 4. 留给子类扩展(如 Servlet 上下文)
    invokeBeanFactoryPostProcessors(beanFactory); // 5. 执行 BFPP,扫描/注册 bean 定义(最关键的扩展点)
    registerBeanPostProcessors(beanFactory);// 6. 实例化并注册所有 BPP(注意: 此处只是注册, 不执行)
    initMessageSource();                   // 7. 国际化
    initApplicationEventMulticaster();     // 8. 事件广播器
    onRefresh();                           // 9. 子类扩展(如内嵌 Web 服务器启动)
    registerListeners();                   // 10. 注册监听器
    finishBeanFactoryInitialization(beanFactory); // 11. 实例化所有非懒加载单例(大头: getBean)
    finishRefresh();                       // 12. 发布 ContextRefreshedEvent, 启动生命周期
}

两个最关键的扩展点:

  • BeanFactoryPostProcessor(步骤 5):在 Bean 实例化之前修改/补充 BeanDefinition。最典型的 ConfigurationClassPostProcessor 就是在这里完成 @ComponentScan 扫描、@Import、@Bean 的解析,把一堆类变成 BeanDefinition 注册进工厂。它能改"类的元数据"。
  • BeanPostProcessor(步骤 6 注册,步骤 11 执行):在 Bean 实例化之后的初始化前后插手,能替换/包装 Bean。AOP 代理就是在这里织入的。它能改"对象本身"。

TIP

理解二者时序差异是读 Spring 源码的钥匙:BFPP 在 Bean 还没出生时改它的"出生证明",BPP 在 Bean 出生后改它的"肉身"。任何"为什么这个 Bean 被换成了代理对象"的问题,答案都在 BPP 里。

二、Bean 生命周期:从构造到销毁 ​

单例 Bean 的创建(getBean → doCreateBean)是一条清晰流水线:

  1. 实例化:反射调用构造器 createBeanInstance(默认无参构造,或有参注入)。
  2. 属性填充:populateBean 处理 @Autowired/@Value(由 AutowiredAnnotationBeanPostProcessor 完成)。
  3. Aware 回调:BeanNameAware/BeanFactoryAware/ApplicationContextAware。
  4. BPP 前置:postProcessBeforeInitialization(如 @PostConstruct 在此触发,由 CommonAnnotationBeanPostProcessor 执行)。
  5. 初始化:InitializingBean.afterPropertiesSet() 或自定义 init-method。
  6. BPP 后置:postProcessAfterInitialization —— AOP 代理就在这里生成(返回代理对象替换原对象)。
  7. 销毁:容器关闭时 DisposableBean.destroy() / @PreDestroy。

三、三级缓存:如何解开循环依赖 ​

经典问题:A 依赖 B,B 又依赖 A,Spring 怎么不死的?答案是三级缓存 + 提前暴露未初始化完成的引用。

java
// DefaultSingletonBeanRegistry 的三个 Map
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();      // 一级: 成品单例
private final Map<String, Object> earlySingletonObjects = new HashMap<>();           // 二级: 早期(已实例化未填充)对象
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();    // 三级: 对象工厂(可生成早期引用, 含 AOP 包装)

解决 A→B→A 的时序:

关键点:三级缓存存的是 ObjectFactory 而非直接对象。为什么要多这一层?因为 A 可能需要被 AOP 代理——只有真正"被别人需要"时(B 来要 A),才通过工厂决定返回"原始早期对象"还是"早期代理对象",避免无谓地提前创建代理。拿到代理后放进二级缓存,删掉三级缓存项。

DANGER

三级缓存只解决单例 + setter/字段注入的循环依赖。构造器注入的循环依赖无法解决——因为对象还没构造出来就无法暴露引用,Spring 会直接抛 BeanCurrentlyInCreationException。@Async 注解的方法也可能让循环依赖失效:因为它是在 postProcessAfterInitialization 才生成代理,而三级缓存提前暴露的是构造期对象,二者可能不一致(Spring 会报"bean 已被代理"的错)。循环依赖本质是设计坏味道,应重构而非依赖框架兜底。

四、AOP:自动代理创建器如何织入 ​

AOP 不是"运行时魔法",而是一个 BeanPostProcessor:AnnotationAwareAspectJAutoProxyCreator(实现了 SmartInstantiationAwareBeanPostProcessor)。在步骤 11 每个 Bean 的 postProcessAfterInitialization 中:

  1. 匹配该 Bean 是否命中任何 @Aspect 的切点(pointcut)。
  2. 命中则按"有接口用 JDK 动态代理,无接口用 CGLIB 子类代理"创建代理。
  3. 代理对象持有 Advisor 列表(切面 + 通知),方法调用经 ReflectiveMethodInvocation 责任链顺序执行:@Before → @AfterReturning/@AfterThrowing → @After → @Around(Around 包裹所有)。
java
// 自调用失效的根因: this 不是代理对象
@Service
public class OrderService {
    public void a() { b(); }                          // this.b() 绕过代理, 切面/事务不生效
    @Transactional public void b() { ... }
}
// 修复: 自注入或通过 AopContext.currentProxy()
((OrderService) AopContext.currentProxy()).b();       // 走代理

五、事务:拦截器链与 ThreadLocal 绑定 ​

@Transactional 的底层是一个 TransactionInterceptor(AOP 通知)。方法进入时:

  1. 从 TransactionAttributeSource 解析事务属性(传播行为、隔离级别、超时、只读)。
  2. 通过 PlatformTransactionManager 开启事务:从数据源拿 Connection,设 autoCommit=false,绑定到 ThreadLocal(TransactionSynchronizationManager)。
  3. 业务执行成功 → commit;抛异常(且匹配 rollbackFor)→ rollback。
  4. 同一线程内的后续 DAO 操作通过 DataSourceUtils.getConnection 拿到同一个被绑定的 Connection,从而实现"一个事务跨多个 Mapper"。
java
// 传播行为 REQUIRES_NEW 源码要点: 挂起当前事务, 新开一个绑定到 ThreadLocal
// PROPAGATION_REQUIRED: 当前有事务则加入, 没有则新建 —— 最常用

WARNING

  • 同类内部方法调用事务/切面失效:this.methodB() 不经过代理,见第四节。必须自注入或 AopContext。
  • @Transactional 标在 private 方法无效:代理只能拦截 public(CGLIB 也只代理 public/protected 可重写方法)。
  • 多数据源事务:默认 DataSourceTransactionManager 只管一个数据源;跨库需用 ChainedTransactionManager(非XA,有不一致窗口)或 Seata 的 AT 模式。
  • 事务内做远程调用/长 IO:连接长时间占用,易引发连接池耗尽与锁等待,事务边界要"小而快"。

📚 官方文档与参考资料 ​

✏️ 练习与自测 ​

  1. 在 refresh() 十二步中,标注 BeanFactoryPostProcessor、BeanPostProcessor、@PostConstruct、AOP 代理分别发生在哪一步,并说明为什么顺序如此。
  2. 构造 A→B→A 字段注入循环,结合三级缓存源码解释为何不报错;再改成构造器注入,解释为何必报 BeanCurrentlyInCreationException。
  3. 写一段同类方法自调用导致 @Transactional 失效的代码,给出两种修复方案并说明原理。
  4. 读 TransactionInterceptor.invokeWithinTransaction,画出"开启连接→绑定 ThreadLocal→commit/rollback"的调用链。

本节小结

Spring 的精髓是"扩展点驱动":refresh() 用模板方法串起生命周期,BFPP 改 BeanDefinition、BPP 改 Bean 对象(AOP 代理在此织入);三级缓存用 ObjectFactory 提前暴露早期引用解开单例循环依赖(构造器注入无解);事务与 AOP 都建立在"动态代理 + ThreadLocal 绑定连接"之上。读懂这些,任何 Spring 衍生框架都是同一套心智模型。