Spring refresh() 源码解析:循环依赖三级缓存与AOP代理
2026/9/9 19:29:23 网站建设 项目流程

不夸张地说,Spring 的refresh()是后端面试里出现频率最高的源码题目之一。这个不到二十行的方法,把 BeanFactory 的创建、BeanDefinition 的加载、后处理器的注册、单例 bean 的实例化、消息源与事件多播器的初始化,甚至循环依赖的兜底和 AOP 代理的产生,全部串在了一条主线上。你哪怕没有完整读过 Spring 源码,只要在源码里定位到AbstractApplicationContext.refresh(),再往下递归几次,基本就能把整个 IOC 容器启动过程摸个七七八八。

这篇文章我想把refresh()从整体到细节完整拆一遍,重点落在两个多数人最容易绕晕的点上:循环依赖的三级缓存到底在解决什么问题、为什么必须是三级而不是二级;以及 AOP 的代理对象是在 bean 生命周期的哪个环节、以什么方式插进来的。适合准备 Spring 源码面试、或者读过部分源码但始终没串成一条线的人。我尽量用画图的方式去讲,代码和流程图都用文字还原,保证比啃源码要友好得多。

1. refresh() 流程全景:容器启动到底拆成几步

1.1 12 个步骤先混个脸熟

AbstractApplicationContext.refresh()是 Spring 容器的核心入口,它在容器启动时被调用,负责把“一个空壳的 ApplicationContext”变成“一个完整可用的 IOC 容器”。方法的整体骨架是固定的,我把关键步骤按顺序列出来,先混个脸熟,后面再逐段深入。

@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 1. 刷新前的准备工作,设置启动时间、活跃标志、初始化属性源 prepareRefresh(); // 2. 获取 BeanFactory,对于 AbstractRefreshableApplicationContext // 会在这里销毁旧容器、创建新容器并加载 BeanDefinition ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 3. 给 BeanFactory 填充标准特性,比如类加载器、SpEL 解析器等 prepareBeanFactory(beanFactory); try { // 4. 子类钩子,允许子类对 BeanFactory 做额外设置 postProcessBeanFactory(beanFactory); // 5. 调用容器中已注册的 BeanFactoryPostProcessor invokeBeanFactoryPostProcessors(beanFactory); // 6. 注册 BeanPostProcessor,注意这里是注册,不是执行 registerBeanPostProcessors(beanFactory); // 7. 初始化国际化资源 MessageSource initMessageSource(); // 8. 初始化事件多播器 ApplicationEventMulticaster initApplicationEventMulticaster(); // 9. 子类钩子,WebApplicationContext 在这里创建内嵌 Web 容器 onRefresh(); // 10. 注册监听器 registerListeners(); // 11. 实例化所有非懒加载的单例 bean,这是最核心的一步 finishBeanFactoryInitialization(beanFactory); // 12. 发布 ContextRefreshedEvent 事件,完成刷新 finishRefresh(); } catch (BeansException ex) { // 刷新失败,销毁已经创建的 bean,重置 active 标志 destroyBeans(); cancelRefresh(ex); throw ex; } finally { // 清掉本次刷新过程中留下的缓存 resetCommonCaches(); } } }

这 12 步里,第 2、3、5、6、11 步对理解 IOC 容器最关键的。第 11 步finishBeanFactoryInitialization是普通 bean 实例化的开始,也是循环依赖和 AOP 真正发生的地方;前面的步骤更多是在为这一步“铺路”。

我给每个步骤配了一个更直观的职责表,方便对照:

步骤方法名核心职责
1prepareRefresh初始化属性源、设置容器启动时间、活跃状态
2obtainFreshBeanFactory获取/创建 BeanFactory,加载 BeanDefinition
3prepareBeanFactory为 BeanFactory 配置类加载器、SpEL、Aware 处理器等
4postProcessBeanFactory子类扩展钩子
5invokeBeanFactoryPostProcessors执行 BeanFactoryPostProcessor,可修改 BeanDefinition
6registerBeanPostProcessors注册 BeanPostProcessor,稍后实例化阶段执行
7initMessageSource初始化国际化资源
8initApplicationEventMulticaster初始化事件广播器
9onRefresh子类扩展钩子(如创建 Web 容器)
10registerListeners注册 ApplicationListener
11finishBeanFactoryInitialization实例化所有非懒加载单例 bean
12finishRefresh发布刷新完成事件、初始化生命周期处理器

1.2 模板方法模式:为什么流程能如此优雅

refresh()本身是一个逻辑清晰的模板方法。Spring 把“容器启动”这件事拆成固定步骤,步骤的执行顺序是不允许子类改变的;但某些步骤内部允许子类插入自己的逻辑,比如第 4 步postProcessBeanFactory和第 9 步onRefresh

这背后的设计动机很朴素:IOC 容器的启动骨架绝大多数场景都一样,但不同应用场景(普通 Java 应用、Web 应用、Spring Boot 内嵌容器)对容器的诉求不同。模板方法模式把不变的部分收敛到父类,把变化的部分留给子类覆写。onRefresh()最常见的实现就是ServletWebServerApplicationContext里启动内嵌 Tomcat/Jetty,这些都是子类差异化能力的体现。

我在读这段源码时最大的感受是,Spring 把生命周期管理的边界做了非常清晰的划分:第一步到第六步都在解决“容器的底座”问题,让容器具备解析配置、创建对象、执行扩展逻辑的能力;第七步到第十步处理“容器协作基础设施”的问题,包括国际化、事件通知;第十一步真正开始按需创建业务对象。读源码如果只盯住某一个方法,很容易迷失;如果先把握整体阶段划分,再看每个阶段里 Spring 在解决什么问题,会顺畅得多。

从调试角度说,如果你在refresh()synchronized (this.startupShutdownMonitor)上打断点,然后单步往下走,可以非常直观地观察到容器状态从无到有的完整过程。后续看循环依赖、AOP 代理时,也可以从第 11 步入手,一路跟到AbstractAutowireCapableBeanFactory.doCreateBean

2. 关键环节钻探:从 BeanFactory 到完备容器

2.1 obtainFreshBeanFactory:BeanDefinition 是怎么被读进来的

第二步obtainFreshBeanFactory()AbstractApplicationContext里是一个模板方法,真正的实现在AbstractRefreshableApplicationContext

@Override protected final void refreshBeanFactory() throws BeansException { // 如果当前容器已经持有 BeanFactory,先销毁并关闭 if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { // 创建新的 DefaultListableBeanFactory DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); // 设置是否允许循环引用,默认 true customizeBeanFactory(beanFactory); // 加载 BeanDefinition,这是把 XML/注解/Java Config 里的 // bean 配置解析成 BeanDefinition 并注册进容器 loadBeanDefinitions(beanFactory); this.beanFactory = beanFactory; } catch (IOException ex) { throw new ApplicationContextException(...); } }

这段代码信息量很大。它每次refresh()都会创建一个全新的DefaultListableBeanFactory,这意味着 BeanDefinition 的注册表是从零开始的。注意customizeBeanFactory(beanFactory)里面有一个allowCircularReferences选项,Spring 默认允许循环引用,后面循环依赖能解决,默认前提在这里就定下了。

再往下走,loadBeanDefinitions在不同子类里有不同实现。XmlWebApplicationContext委托给XmlBeanDefinitionReaderAnnotationConfigWebApplicationContextAnnotationConfigApplicationContext则通过ClassPathBeanDefinitionScanner扫描包路径,把标了@Component@Service@Repository等注解的类解析成ScannedGenericBeanDefinition并注册进容器。

这一步完成后,DefaultListableBeanFactorybeanDefinitionMap里已经塞满了各种 BeanDefinition,但此时还没有任何一个“真正的 bean 对象”被创建。可以理解为,所有 bean 的“图纸”已经归档到工厂,但流水线还没有开始生产。

2.2 prepareBeanFactory:给容器装上常用技能

第三步prepareBeanFactory(beanFactory)是花了很多心思的基础设施装配。它做的事情如果用一句话概括:让一个“裸 BeanFactory”变成一个“懂 Spring 规范的 BeanFactory”。

protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) { // 设置类加载器 beanFactory.setBeanClassLoader(getClassLoader()); // 注册 SpEL 表达式解析器 beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver()); // 注册属性编辑器注册器 beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment())); // 添加 ApplicationContextAwareProcessor, // 让 bean 可以通过实现 Aware 接口拿到 ApplicationContext 等容器对象 beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this)); // 下面这些 Aware 接口会被自动忽略,因为它们由 ApplicationContextAwareProcessor 处理 beanFactory.ignoreDependencyInterface(EnvironmentAware.class); beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class); beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class); beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class); beanFactory.ignoreDependencyInterface(MessageSourceAware.class); beanFactory.ignoreDependencyInterface(ApplicationContextAware.class); // 注册几个可解析的特殊依赖类型 beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory); beanFactory.registerResolvableDependency(ResourceLoader.class, this); beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this); beanFactory.registerResolvableDependency(ApplicationContext.class, this); // 注册 ApplicationListenerDetector,用于在 bean 创建完后检测 ApplicationListener beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this)); // 如果存在 LoadTimeWeaver,则注册相关后处理器,AOP 织入时会用到 if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) { beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory)); beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader())); } // 注册默认环境 bean if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) { beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment()); } // 注册 systemProperties、systemEnvironment if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) { beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties()); } if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) { beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment()); } }

这里最值得关注的是ApplicationContextAwareProcessor。它是一个BeanPostProcessor,负责给实现了ApplicationContextAwareEnvironmentAwareResourceLoaderAware等接口的 bean 注入对应对象。Spring 之所以拒绝直接用@Autowired ApplicationContext,而是推荐实现ApplicationContextAware,是因为后者的语义更明确、更符合容器规范,而@Autowired在非 Spring 容器环境下无法工作。ignoreDependencyInterface则是告诉自动装配机制“这几个接口你别管,我有专门的处理器来处理”,避免重复注入。

顺带一提,这段代码还注册了systemPropertiessystemEnvironment两个单例 bean。这意味着你在业务代码里可以直接@Autowired一个名为systemPropertiesMap,拿到 JVM 系统属性。这个设计很小,但能看出 Spring 连“系统级依赖”都统一塞进了容器,所有 bean 在同一套依赖体系里平等协作。

2.3 后处理器的注册:invoke 与 register 的先后差异

第五步和第六步是很多初学者容易搞混的地方:invokeBeanFactoryPostProcessorsregisterBeanPostProcessors到底有什么区别?

从名字上就能看出关键差异:一个是“invoke(执行)”,一个是“register(注册)”。BeanFactoryPostProcessor在容器启动早期就执行,它的执行时机在“所有 bean 实例化之前”,因此它有机会去修改 BeanDefinition。比如PropertySourcesPlaceholderConfigurer就是一个典型的BeanFactoryPostProcessor,它把@Value("${xxx}")里的占位符替换成真实的配置值,如果你配置了外部配置文件,就是在这里被解析进容器的。

BeanPostProcessor则不一样。第六步只是把它注册进容器,真正的执行发生在之后 bean 实例化的过程中,分别在 bean 初始化前(postProcessBeforeInitialization)和初始化后(postProcessAfterInitialization)被调用。AOP 自动代理器AnnotationAwareAspectJAutoProxyCreator就是一个BeanPostProcessor,它正是靠postProcessAfterInitialization在 bean 初始化完成后把代理对象包装出来的。

// 大概的调用逻辑: invokeBeanFactoryPostProcessors(beanFactory); // 立即调用 PostProcessor 的方法 registerBeanPostProcessors(beanFactory); // 把 PostProcessor 放进一个 List,等实例化时再回调

为什么BeanFactoryPostProcessor要立即执行?因为后续无论是注册 BeanPostProcessor 还是实例化 bean,都需要依赖已经处理好的 BeanDefinition 元信息。如果@Value占位符没有先替换,后面注入属性时就会拿到错误的字面量。BeanPostProcessor则没有这个优先级问题,它可以等 bean 创建过程中再介入,自然就往后放。

3. 循环依赖与三级缓存:把“先有鸡还是先有蛋”变成现实

3.1 循环依赖什么时候会炸

循环依赖指的是 A 依赖 B、B 又依赖 A。在 Spring 里,循环依赖能不能解决,取决于注入方式。

  • 构造器注入:A 的构造器需要 B,B 的构造器需要 A。创建 A 时必须先有 B,创建 B 时又必须先有 A,这就形成了无解的死锁。Spring 无法通过提前曝光解决,因为构造器执行完之前,对象都还不存在,没有“半成品”可以拿去引用。这种场景会直接抛BeanCurrentlyInCreationException
  • setter 注入 / 字段注入:A 可以先实例化出来(此时内部属性还没有值),再通过populateBean填充属性。填充属性时发现需要 B,就去创建 B;创建 B 时发现需要 A,但此时 A 已经被“提前曝光”到了三级缓存中,B 能直接拿到 A 的引用。这正是 Spring 解决循环依赖的经典路径。

我随便写一个场景:

@Component public class A { @Autowired private B b; } @Component public class B { @Autowired private A a; }

这个例子几乎是面试必问。A 和 B 都是单例、字段注入,Spring 能正常创建出来。但如果把@Autowired改成构造器注入,同样的两个类,Spring 启动时就会报创建异常。

3.2 三级缓存每一级各自存什么

DefaultSingletonBeanRegistry里定义了三个核心缓存,也就是常说的三级缓存:

/** 一级缓存:存放完整的成品 bean */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** 二级缓存:存放提前曝光的早期 bean 引用,是半成品 */ private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); /** 三级缓存:存放 ObjectFactory,用于延迟生成早期引用 */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
缓存级别缓存结构存放内容生命周期
一级singletonObjectsConcurrentHashMap完整初始化好的单例 bean,也就是getBean的最终产物容器关闭前常驻
二级earlySingletonObjectsConcurrentHashMap提前创建出来的原始 bean 实例(可能未完成属性填充),缓存级别明确,防止多线程下重复创建从三级缓存提升后放入
三级singletonFactoriesHashMapObjectFactory工厂,调用getObject()时才会生成早期引用(这里可能生成 AOP 代理)在创建完成前一直存在,完成后移除

三个缓存的读取顺序非常关键:先查一级,再查二级,最后查三级。三级缓存里的ObjectFactory一旦被调用,得到的对象会“提升”到二级缓存,同时从三级缓存移除,也就是说,一个 bean 的早期引用在整个创建周期中只会被创建一次。

3.3 核心时序:A 和 B 是怎么完成互相引用的

我用最典型的 A、B 循环依赖场景,把整个创建流程逐步拆开。

第一步,容器准备创建 A,getSingleton(beanName)查一级缓存发现没有 A,于是调用getSingleton(beanName, () -> createBean(...)),A 进入singletonsCurrentlyInCreation集合。

第二步,createBean创建出 A 的原始实例,此时 A 的属性(B)还没有填充。在populateBean之前,Spring 做了一次提前曝光:

boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); }

所谓“提前曝光”,就是把一个ObjectFactory放进三级缓存singletonFactories。这个ObjectFactory在被调用时会执行getEarlyBeanReference,这个方法后面和 AOP 有非常深的关联。

第三步,populateBean开始填充 A 的属性,发现需要 B,于是调用getBean(B)。此时 B 尚未创建,重复 A 的创建流程,B 被实例化并提前曝光。

第四步,填充 B 的属性时发现需要 A,再次调用getBean(A)getBean内部走getSingleton(beanName, allowEarlyReference=true)

protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); // 一级缓存没有,并且这个 bean 正在创建中 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { synchronized (this.singletonObjects) { // 双重检查,防止并发重复创建 singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { // 调用三级缓存的 ObjectFactory,生成 A 的早期引用 singletonObject = singletonFactory.getObject(); // 提升到二级缓存,移除三级缓存 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } } } return singletonObject; }

因为 A 的singletonFactories中已经存在对应的ObjectFactory,所以 B 能拿到 A 的早期引用。这个早期引用被放入二级缓存,同时从三级缓存移除。B 把这个引用注入到自己的属性里,B 创建完成。

第五步,A 的createBean流程继续,B 已经存在,A 把 B 注入属性中,A 创建完成。

第六步,A 创建完成后进入addSingleton,把成品 A 放入一级缓存,同时把二级缓存、三级缓存里的 A 清理掉。B 也同理,最终一级缓存中存有 A 和 B 的完整实例。

这个过程用文字描述会比较绕,我建议你亲自 debug 一遍getSingleton方法,观察singletonObjectsearlySingletonObjectssingletonFactories三个 map 的变化。

3.4 为什么一定是三级缓存,而不是二级

这是循环依赖部分最经典的问题:既然二级缓存已经能存早期引用,为什么非要再加一个三级缓存?

网上很多解释会说“三级缓存是为了 AOP”,这个说法方向是对的,但不完整。实际上,在没有 AOP 的场景下,二级缓存确实就够用了。A 提前曝光时直接把原始实例放进二级缓存,B 拿到的就是 A 的原始实例,后续 A 完成初始化后再把成品放进一级缓存,A 始终只有一个实例,引用也是同一个。

麻烦就麻烦在 AOP。假如 A 需要被增强,Spring 期望 B 注入的 A 是代理对象,而不是裸对象;如果 A 不是循环依赖的起点,只是普通 bean,那么代理会在postProcessAfterInitialization阶段生成,完成时一级缓存存的是代理对象。现在 A 作为循环依赖的一环,B 提前拿走了 A 的引用。如果拿的是原始对象,那 B 持有的是“未增强的 A”,而容器最终保存的是“增强后的 A”,同一个 bean 在容器里出现了两个不同的实例,AOP 失效,甚至可能导致业务逻辑出错。

三级缓存正是为了解决这个矛盾。提前曝光进三级缓存的不是原始对象,而是一个ObjectFactory,这个工厂在生成早期引用时会调用getEarlyBeanReference

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { // 逐个调用 SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) { exposedObject = bp.getEarlyBeanReference(exposedObject, beanName); } } return exposedObject; }

AbstractAutoProxyCreator是 AOP 自动代理的核心实现类,它实现了getEarlyBeanReference,在里面判断当前 bean 是否需要代理,如果需要就提前创建代理对象并返回。这样一来,B 拿到的 A 已经是代理对象,容器最终保存的也是同一个代理对象,身份统一,AOP 也生效。

如果只有二级缓存,Spring 就必须在 A 实例化完成后立刻决定是否生成代理,并把代理放进二级缓存。但此时 bean 的属性还没填充、初始化还没执行,提前做代理判断会破坏 AOP 织入的时机,还会让那些没有循环依赖又需要完整生命周期的普通 bean 背上不必要的代理创建成本。三级缓存把这个决策延迟到“被引用”的那一刻,只有真正发生循环依赖引用时才触发ObjectFactory.getObject(),堪称懒加载思路的经典应用。

我自己的理解是:三级缓存本质上是“用工厂延迟生成”来换取“代理时机的精确性”。它不一定要生成代理对象,很多场景下getEarlyBeanReference返回的就是原始 bean;但它在需要 AOP 时能够生成正确的代理,这就解决了二级缓存解决不了的核心矛盾。

4. AOP 在 bean 生命周期里的完整落点

4.1 AOP 自动代理器是怎么被注册进去的

AOP 不是 Spring IOC 容器预先内置的能力,而是通过BeanPostProcessor机制“寄生”进 bean 生命周期的。最核心的类就是AnnotationAwareAspectJAutoProxyCreator

如果你用注解驱动的方式开启 AOP,通常会在配置类上标注@EnableAspectJAutoProxy。这个注解通过@Import(AspectJAutoProxyRegistrar.class)在容器中注册一个AnnotationAwareAspectJAutoProxyCreator的 BeanDefinition。如果项目是 Spring Boot,@SpringBootApplication组合了@EnableAutoConfiguration,自动配置里也会注册 AOP 自动代理器,所以平时开箱即用。

public class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // 注册 AnnotationAwareAspectJAutoProxyCreator AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry); // ... 解析 @EnableAspectJAutoProxy 的 proxyTargetClass 等属性 } }

AnnotationAwareAspectJAutoProxyCreator的继承体系非常关键:

AnnotationAwareAspectJAutoProxyCreator extends AspectJAwareAdvisorAutoProxyCreator extends AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyCreator implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware

注意最后一行,它实现了SmartInstantiationAwareBeanPostProcessor,这是一个特殊的BeanPostProcessor,额外提供了getEarlyBeanReference方法。正因为有这层实现,AOP 代理器才能在前文的三级缓存机制里扮演“提前生成代理”的角色。

4.2 代理创建的两次触发:getEarlyBeanReference 与 postProcessAfterInitialization

AbstractAutoProxyCreator里有两条创建代理的路径,对应两个来源。

路径一:循环依赖触发。当 bean 被提前曝光且其他 bean 需要引用它时,三级缓存的ObjectFactory.getObject()会调用getEarlyBeanReferenceAbstractAutoProxyCreator重写了这个方法:

@Override public Object getEarlyBeanReference(Object bean, String beanName) { Object cacheKey = getCacheKey(bean.getClass(), beanName); // 记录这个 bean 已经被提前代理过了 this.earlyProxyReferences.put(cacheKey, Boolean.TRUE); // 如果需要代理,这里直接 wrap 成代理对象 return wrapIfNecessary(bean, beanName, cacheKey); }

wrapIfNecessary是 AOP 代理创建的核心方法,它内部会去找匹配当前 bean 的 Advisor(通知器),如果找到就创建代理对象,找不到就直接返回原对象。

路径二:普通初始化完成后触发。对于没有循环依赖的 bean,代理发生在postProcessAfterInitialization

@Override public Object postProcessAfterInitialization(Object bean, String beanName) { if (bean != null) { Object cacheKey = getCacheKey(bean.getClass(), beanName); // 如果这个 bean 已经被提前代理过了,这里不再重复代理 if (this.earlyProxyReferences.remove(cacheKey) != Boolean.TRUE) { return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; }

这段代码里有一个非常容易忽略的细节:earlyProxyReferences这个 map 是关键中的关键。如果一个 bean 在循环依赖阶段已经被getEarlyBeanReference包装成代理对象,那么等它走完生命周期、进入postProcessAfterInitialization时,会先从earlyProxyReferences中移除记录,发现已经代理过,就直接返回原始 bean。但这里有个坑:如果返回的是原始 bean,那容器里存的不就错了吗?

实际上不会错。因为在循环依赖场景下,从三级缓存取出并放入二级缓存的“早期引用”已经是代理对象了;后续这个代理对象会被继续填充属性、初始化,然后被addSingleton放入一级缓存。postProcessAfterInitialization返回的原始对象虽然在这次调用中被忽略了,但最终进入一级缓存的是那个早期代理对象,因为doCreateBean后续步骤用的是exposedObject,它在提前曝光时已经被替换成了代理对象。

简单总结一下:

  • getEarlyBeanReference负责“被引用前保证拿到代理”;
  • postProcessAfterInitialization负责“正常流程中生成代理”;
  • earlyProxyReferences负责“两种路径下不重复代理”。

4.3 一个同时涉及循环依赖与 AOP 的例子

我把 3.3 节的 A、B 循环依赖升级一下:A 上加了事务切面,B 上没有。

A 创建进入循环依赖流程后,提前曝光时三级缓存里的ObjectFactory调用getEarlyBeanReference,发现 A 匹配了事务 Advisor,于是直接生成 A 的 CGLIB 代理对象,放进二级缓存。B 拿到的就是 A 的代理对象。

之后 A 继续完成属性填充、初始化。postProcessAfterInitialization执行时,因为earlyProxyReferences里已经有 A 的缓存记录,所以不会再次包装。最终addSingleton把 A 的代理对象放进一级缓存。

这样一来,容器里 A 只有一个实例,也就是代理对象;B 持有的是同一个代理对象;A 的事务方法也能在 B 的调用中生效。如果三级缓存退化成了二级缓存,A 的代理无法提前生成,B 拿到的是原始 A,那么 B 调 A 的方法时事务切面完全不会执行,这就是很多人遇到的“循环依赖下 AOP 失效”的底层原因。

这里还有一个隐藏的细节值得注意:Spring 默认允许循环引用(allowCircularReferences=true),如果你明确知道自己不需要循环依赖,出于规范考虑可以在配置里关闭它。关闭后,Spring 遇到循环依赖直接报错,这在大型项目中往往比“能跑但隐患很大”更好。

5. 高频问题与排查经验

5.1 “BeanCurrentlyInCreationException”到底怎么办

这是循环依赖场景下最典型的报错,我遇到过的排查路径大概有这么几种。

先看报错信息里有没有Requested bean is currently in creation这类文字,有的话基本能确认是循环依赖。然后去定位循环链:A -> B -> A,或者 A -> B -> C -> A。多数情况下是构造器注入导致的,处理办法有三个方向:

  • 改成 setter 注入或字段注入。这是最快速的处理方式,但要注意它治标不治本,只是在利用 Spring 的循环依赖兜底能力。
  • 在其中一个依赖上加上@Lazy@Lazy会生成一个代理对象注入进去,真正调用时才去容器获取目标对象,从而打破循环。这个方案在构造器注入里也很好用,因为@Lazy代理可以让构造器参数拿到一个“替身”,不触发目标 bean 的创建。
  • 重新审视依赖设计。很多时候 A 依赖 B、B 依赖 A 本身就说明职责划分有问题,把相互依赖的公共逻辑抽到第三个类里,是最推荐的做法。

如果项目里已经大概率没有循环依赖,直接在配置里关闭循环引用反而更安全:

@Bean public static BeanFactoryPostProcessor disableCircularReferences() { return beanFactory -> { if (beanFactory instanceof DefaultListableBeanFactory) { ((DefaultListableBeanFactory) beanFactory).setAllowCircularReferences(false); } }; }

这样一旦有人新加入循环依赖,应用启动时就会立即暴露,而不是藏着隐患运行。

5.2 AOP 没生效的几个常见原因

排除那些“切面表达式写错”的低级问题后,我遇到比较隐蔽的原因有三个。

  • 自调用问题。同一个类里methodA()methodB(),如果methodB()@Transactional@Async,AOP 不会生效。因为自调用走的是this引用,不是代理对象。解决办法是把methodB拆分到另一个 bean,或者注入自身代理对象。
  • 循环依赖伴随的 AOP 失效。这个恰恰是本文前面讲的核心机制没有生效的体现。虽然 Spring 三级缓存设计上已经能覆盖普通 AOP,但如果你在扩展点里动了getEarlyBeanReference,或者没有走SmartInstantiationAwareBeanPostProcessor的代理器,就可能出现拿到非代理对象的情况。
  • Spring Boot 3/4 时代 AOP starter 问题。Spring Boot 里默认spring-boot-starter-aop引用的切面机制一般没问题,但如果你用了自定义AutoProxyCreator,要注意它和 Spring Boot 自动配置的兼容性。现实中很多“找不到 AOP 相关类”的报错,是缺少aspectjweaver依赖导致的。

排查建议很简单:在postProcessAfterInitializationgetEarlyBeanReference上打断点,看看目标 bean 有没有走进wrapIfNecessary,以及Advisor是否匹配成功。

5.3 源码阅读的一些实用建议

如果你打算把refresh()完整啃下来,我建议你按这个顺序走:

先读refresh()骨架,配合 debug 看完整 12 步的日志输出。然后在finishBeanFactoryInitialization里打断点,进入preInstantiateSingletons。这些方法是在第 11 步里触发的:

finishBeanFactoryInitialization(beanFactory) -> preInstantiateSingletons() -> getBean(beanName) -> doGetBean() -> getSingleton() -> createBean() -> doCreateBean()

doCreateBean是核心中的核心,bean 的生命周期基本都能在这里看到:实例化、提前曝光、属性填充、初始化、注册销毁方法。循环依赖的缓存变化也主要发生在这个方法及其调用的getSingletonpopulateBean中。

debug 时我习惯在三个位置加观察表达式:singletonObjectsearlySingletonObjectssingletonFactories。你会在 bean 创建过程中直观看到数据在不同缓存里的“搬运”,比任何文字描述都管用。

还有个经验是,源码版本尽量选 5.x 的稳定版,比如 Spring Framework 5.3.x。5.3 的代码已经非常成熟,读起来结构清晰,网上能找到的资料也最多,排查问题时容易对照。

6. 写在最后的实践体会

源码读多了你会发现,refresh()里没有太多高深莫测的技巧,更多的是对“对象生命周期管理”极其精细的设计。三级缓存和 AOP 代理器的配合,本质上是在打包一个庞大的对象图,同时还要兼顾循环引用、代理增强、线程安全、懒加载等各种约束条件。我每次读这段源码都会对 Spring 团队的设计能力多一分敬意。

一点个人经验:如果你是想为面试准备这段内容,不必死记方法名,而是先掌握“为什么”的脉络。为什么有三级缓存,为什么BeanPostProcessor要先注册后执行,为什么 AOP 代理器要实现SmartInstantiationAwareBeanPostProcessor,这几个问题想通了,源码流程自然而然就能串起来。真正去公司面试时,面试官看重的是你把机制讲清楚的能力,而不是背诵了多少行源码。

最后分享一个小技巧:读源码卡壳的时候,我习惯写一个最简单的 demo,只有两个 bean、一个切面、一次断点,然后反复重启应用观察调用栈。Spring 源码的调用链虽然长,但只要从一个具体入口出发,顺着调用栈一层层往回找,大多数问题都能在五分钟内定位到对应代码段。这种方式比从头到尾通读源码高效得多,也更容易形成自己的理解。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询