Spring源码解读案例

wen java案例 1

本文目录导读:

Spring源码解读案例

  1. 目录导读
  2. 引言:为什么必须读Spring源码?
  3. 案例一:Bean的生命周期——完整旅程拆解
  4. 案例二:循环依赖——三级缓存如何破局
  5. 案例三:@Transactional失效之谜——代理机制拆解
  6. 高频面试问答:基于源码的深度Q&A
  7. 结语:源码阅读的“三步法”与思维模型

Spring源码解读案例:从Bean生命周期到循环依赖的底层真相

目录导读

  1. 引言:为什么必须读Spring源码?
  2. Bean的生命周期——一个对象从出生到销毁的完整旅程
  3. 循环依赖——三级缓存如何破解“先有鸡还是先有蛋”
  4. @Transactional失效之谜——代理机制源码级拆解
  5. 高频面试问答:基于源码的深度Q&A
  6. 源码阅读的“三步法”与思维模型

引言:为什么必须读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代理)

破解逻辑

  1. 创建A → 实例化原始A → 放入三级缓存(singletonFactories
  2. 填充属性B → 发现B未创建 → 创建B
  3. B填充属性A → 从三级缓存获取A的ObjectFactory → 调用getObject()拿到早期A(若需AOP则生成代理)→ 放入二级缓存
  4. B创建完成 → A成功注入B

2 为何需要三级缓存?

如果直接用二级缓存(提前暴露原始对象),无法处理AOP代理,因为代理对象必须在依赖注入前生成,但此时AOP切面可能尚未准备好,三级缓存通过ObjectFactory延迟到真正需要依赖时才生成代理,实现“按需代理”

注意:只有单例Bean支持循环依赖,prototype作用域会直接抛出异常。


案例三:@Transactional失效之谜——代理机制拆解

经典Bugthis.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的代理对象。源码位于MapperFactoryBeancheckDaoConfig()SqlSessionDaoSupport

Q4:如何通过源码判断一个Bean是哪个后处理器修改的?

开启debug日志,搜索BeanPostProcessorapplyBeanPostProcessorsBeforeInitialization调用栈,观察对应类的postProcessBeforeInitialization实现。


源码阅读的“三步法”与思维模型

三步法

  1. 跑起来:写一个最小Demo,打断点观察。
  2. 画时序图:用ProcessOn画出关键方法调用链。
  3. 提问式阅读:不断问“为什么这样设计”,对比其他IOC容器(如Guice)。

思维模型

  • 模板方法模式AbstractApplicationContext.refresh() 定义了容器启动的骨架。
  • 策略模式Resource 接口封装不同资源访问。
  • 责任链模式BeanPostProcessor 链式调用。

最终建议:源码阅读不必追求100%覆盖,选取核心循环依赖、事务、AOP三大主线即可覆盖80%面试题,当你精读过这一篇,下回遇到“BeanNameAware回调在何时执行”时,你不必死记,而是能推导出“它属于Aware回调,一定在属性填充前”。

动手打开你的IDE,在doCreateBean()处打一个断点,亲眼看看Spring是如何“分娩”一个Bean的——那种顿悟感,比任何源码解读文章都来得震撼。

抱歉,评论功能暂时关闭!