本文目录导读:

- 目录导读
- 引言:为什么必须读Spring源码?
- 案例一:Bean的生命周期——完整旅程拆解
- 案例二:循环依赖——三级缓存如何破局
- 案例三:@Transactional失效之谜——代理机制拆解
- 高频面试问答:基于源码的深度Q&A
- 结语:源码阅读的“三步法”与思维模型
Spring源码解读案例:从Bean生命周期到循环依赖的底层真相
目录导读
- 引言:为什么必须读Spring源码?
- Bean的生命周期——一个对象从出生到销毁的完整旅程
- 循环依赖——三级缓存如何破解“先有鸡还是先有蛋”
- @Transactional失效之谜——代理机制源码级拆解
- 高频面试问答:基于源码的深度Q&A
- 源码阅读的“三步法”与思维模型
引言:为什么必须读Spring源码?
在Java工程师的面试中,“看过Spring源码吗”几乎是必考题。但真正理解源码并不意味着背诵类名和方法名,而是掌握其设计哲学,根据Stack Overflow 2024年调查,Spring Boot仍是全球最流行的Java框架,占比高达82%。源码解读案例的价值在于:当你遇到性能瓶颈、诡异Bug或框架扩展需求时,只有理解底层才能游刃有余。
本文通过3个经典案例,带你看透Spring的核心机制,所有代码基于Spring Framework 6.x版本。
案例一:Bean的生命周期——完整旅程拆解
场景:我们定义一个简单的UserService,通过@Bean注册,观察其创建过程。
1 核心源码路径
AbstractAutowireCapableBeanFactory.doCreateBean() 是生命周期的心脏,执行顺序如下:
实例化(createBeanInstance) → 属性填充(populateBean) → 初始化(initializeBean)
2 关键时刻表
| 阶段 | 回调时机 | 典型用途 |
|---|---|---|
BeanNameAware.setBeanName() |
属性填充前 | 获取beanName |
BeanFactoryAware.setBeanFactory() |
同上 | 获取容器引用 |
@PostConstruct |
初始化前 | 自定义校验 |
InitializingBean.afterPropertiesSet() |
初始化中 | 底层初始化逻辑 |
Aware回调 |
初始化后 | 获取ApplicationContext |
DisposableBean.destroy() |
容器关闭 | 释放资源 |
3 深度洞察
关键在于:populateBean() 阶段会自动处理@Autowired,而initializeBean() 内嵌了AOP代理生成逻辑。这意味着:AOP代理对象是在初始化最后阶段包裹原生Bean的。
// 简化代码
protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) {
invokeAwareMethods(beanName, bean); // 先Aware回调
Object wrappedBean = bean;
// 执行@PostConstruct和afterPropertiesSet
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
invokeInitMethods(beanName, wrappedBean, mbd);
// 关键:这里生成代理
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
return wrappedBean;
}
案例二:循环依赖——三级缓存如何破局
经典问题:A依赖B,B也依赖A,Spring如何解决?
1 三级缓存设计
源码位于DefaultSingletonBeanRegistry:
| 缓存容器 | 作用 | |
|---|---|---|
singletonObjects |
一级缓存 | 完整单例Bean |
earlySingletonObjects |
二级缓存 | 早期暴露的原始Bean(未填充属性) |
singletonFactories |
三级缓存 | ObjectFactory工厂(可生成AOP代理) |
破解逻辑:
- 创建A → 实例化原始A → 放入三级缓存(
singletonFactories) - 填充属性B → 发现B未创建 → 创建B
- B填充属性A → 从三级缓存获取A的ObjectFactory → 调用
getObject()拿到早期A(若需AOP则生成代理)→ 放入二级缓存 - B创建完成 → A成功注入B
2 为何需要三级缓存?
如果直接用二级缓存(提前暴露原始对象),无法处理AOP代理,因为代理对象必须在依赖注入前生成,但此时AOP切面可能尚未准备好,三级缓存通过ObjectFactory延迟到真正需要依赖时才生成代理,实现“按需代理”。
注意:只有单例Bean支持循环依赖,
prototype作用域会直接抛出异常。
案例三:@Transactional失效之谜——代理机制拆解
经典Bug:this.selfInvocation() 调用同类方法,事务不生效。
1 根因分析
Spring事务基于动态代理,当方法被@Transactional标记时,Spring注入的是代理对象(Proxy),而非原始对象。
@Service
public class OrderService {
@Transactional
public void order() {
// 事务生效:通过代理对象调用
}
public void selfCall() {
order(); // 事务失效:this是原始对象!
}
}
源码佐证:AbstractAutowireCapableBeanFactory在初始化阶段,通过BeanPostProcessor(如AnnotationAwareAspectJAutoProxyCreator)将代理对象注入容器。
2 解决方案
- 注入自身:
@Autowired private OrderService self;然后调用self.order()。 - 使用AopContext:
((OrderService) AopContext.currentProxy()).order(),需配置exposeProxy=true。
高频面试问答:基于源码的深度Q&A
Q1:Spring如何保证单例Bean的线程安全?
源码视角:
singletonObjects使用ConcurrentHashMap保证可见性,但Bean实例本身的线程安全不做保证(默认无状态)。真正的线程安全依赖设计(如无状态Service)。
Q2:构造器注入和@Autowired注入,在源码层面有何区别?
构造器注入在
createBeanInstance阶段处理,此时Bean未完全初始化,无法解决循环依赖;@Autowired在populateBean阶段处理,可借助三级缓存解决。
Q3:为什么MyBatis的Mapper接口不需要实现类?
因为Spring通过
FactoryBean+ JDK动态代理,在getObject()中返回Mapper的代理对象。源码位于MapperFactoryBean的checkDaoConfig()和SqlSessionDaoSupport。
Q4:如何通过源码判断一个Bean是哪个后处理器修改的?
开启
debug日志,搜索BeanPostProcessor的applyBeanPostProcessorsBeforeInitialization调用栈,观察对应类的postProcessBeforeInitialization实现。
源码阅读的“三步法”与思维模型
三步法:
- 跑起来:写一个最小Demo,打断点观察。
- 画时序图:用ProcessOn画出关键方法调用链。
- 提问式阅读:不断问“为什么这样设计”,对比其他IOC容器(如Guice)。
思维模型:
- 模板方法模式:
AbstractApplicationContext.refresh()定义了容器启动的骨架。 - 策略模式:
Resource接口封装不同资源访问。 - 责任链模式:
BeanPostProcessor链式调用。
最终建议:源码阅读不必追求100%覆盖,选取核心循环依赖、事务、AOP三大主线即可覆盖80%面试题,当你精读过这一篇,下回遇到“BeanNameAware回调在何时执行”时,你不必死记,而是能推导出“它属于Aware回调,一定在属性填充前”。
动手打开你的IDE,在doCreateBean()处打一个断点,亲眼看看Spring是如何“分娩”一个Bean的——那种顿悟感,比任何源码解读文章都来得震撼。