Spring 源码剖析
Java 高级 · 底层原理
多数工程师只停留在"会用注解",但 Spring 真正的魅力在 refresh() 这一条主线上:它把"对象的创建、依赖的注入、代理的织入、事务的开启"全部编排成可扩展的生命周期。本文按"容器启动 → 循环依赖 → AOP 自动代理 → 事务拦截器链"四步拆解源码脉络,让你读任何 Spring 衍生框架(Spring Boot、Spring Cloud)都有抓手。
一、refresh():容器启动的十二步
AbstractApplicationContext.refresh() 是 IoC 容器的总导演。它不是一个方法,而是一连串"模板 + 扩展点":
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)是一条清晰流水线:
- 实例化:反射调用构造器
createBeanInstance(默认无参构造,或有参注入)。 - 属性填充:
populateBean处理@Autowired/@Value(由AutowiredAnnotationBeanPostProcessor完成)。 - Aware 回调:
BeanNameAware/BeanFactoryAware/ApplicationContextAware。 - BPP 前置:
postProcessBeforeInitialization(如@PostConstruct在此触发,由CommonAnnotationBeanPostProcessor执行)。 - 初始化:
InitializingBean.afterPropertiesSet()或自定义init-method。 - BPP 后置:
postProcessAfterInitialization—— AOP 代理就在这里生成(返回代理对象替换原对象)。 - 销毁:容器关闭时
DisposableBean.destroy()/@PreDestroy。
三、三级缓存:如何解开循环依赖
经典问题:A 依赖 B,B 又依赖 A,Spring 怎么不死的?答案是三级缓存 + 提前暴露未初始化完成的引用。
// 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 中:
- 匹配该 Bean 是否命中任何
@Aspect的切点(pointcut)。 - 命中则按"有接口用 JDK 动态代理,无接口用 CGLIB 子类代理"创建代理。
- 代理对象持有
Advisor列表(切面 + 通知),方法调用经ReflectiveMethodInvocation责任链顺序执行:@Before → @AfterReturning/@AfterThrowing → @After → @Around(Around 包裹所有)。
// 自调用失效的根因: 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 通知)。方法进入时:
- 从
TransactionAttributeSource解析事务属性(传播行为、隔离级别、超时、只读)。 - 通过
PlatformTransactionManager开启事务:从数据源拿Connection,设autoCommit=false,绑定到ThreadLocal(TransactionSynchronizationManager)。 - 业务执行成功 →
commit;抛异常(且匹配 rollbackFor)→rollback。 - 同一线程内的后续 DAO 操作通过
DataSourceUtils.getConnection拿到同一个被绑定的 Connection,从而实现"一个事务跨多个 Mapper"。
// 传播行为 REQUIRES_NEW 源码要点: 挂起当前事务, 新开一个绑定到 ThreadLocal
// PROPAGATION_REQUIRED: 当前有事务则加入, 没有则新建 —— 最常用WARNING
- 同类内部方法调用事务/切面失效:
this.methodB()不经过代理,见第四节。必须自注入或AopContext。 @Transactional标在 private 方法无效:代理只能拦截 public(CGLIB 也只代理 public/protected 可重写方法)。- 多数据源事务:默认
DataSourceTransactionManager只管一个数据源;跨库需用ChainedTransactionManager(非XA,有不一致窗口)或 Seata 的 AT 模式。 - 事务内做远程调用/长 IO:连接长时间占用,易引发连接池耗尽与锁等待,事务边界要"小而快"。
📚 官方文档与参考资料
- Spring Framework 官方文档:https://docs.spring.io/spring-framework/reference/
- Spring 中文文档(社区):https://springdoc.cn/
- Spring 源码(GitHub):https://github.com/spring-projects/spring-framework
✏️ 练习与自测
- 在
refresh()十二步中,标注BeanFactoryPostProcessor、BeanPostProcessor、@PostConstruct、AOP 代理分别发生在哪一步,并说明为什么顺序如此。 - 构造
A→B→A字段注入循环,结合三级缓存源码解释为何不报错;再改成构造器注入,解释为何必报BeanCurrentlyInCreationException。 - 写一段同类方法自调用导致
@Transactional失效的代码,给出两种修复方案并说明原理。 - 读
TransactionInterceptor.invokeWithinTransaction,画出"开启连接→绑定 ThreadLocal→commit/rollback"的调用链。
本节小结
Spring 的精髓是"扩展点驱动":refresh() 用模板方法串起生命周期,BFPP 改 BeanDefinition、BPP 改 Bean 对象(AOP 代理在此织入);三级缓存用 ObjectFactory 提前暴露早期引用解开单例循环依赖(构造器注入无解);事务与 AOP 都建立在"动态代理 + ThreadLocal 绑定连接"之上。读懂这些,任何 Spring 衍生框架都是同一套心智模型。