ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Spring refresh() 源码解析:循环依赖三级缓存与AOP代理

Spring refresh() 源码解析:循环依赖三级缓存与AOP代理 不夸张地说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加载 BeanDefinition3prepareBeanFactory为 BeanFactory 配置类加载器、SpEL、Aware 处理器等4postProcessBeanFactory子类扩展钩子5invokeBeanFactoryPostProcessors执行 BeanFactoryPostProcessor可修改 BeanDefinition6registerBeanPostProcessors注册 BeanPostProcessor稍后实例化阶段执行7initMessageSource初始化国际化资源8initApplicationEventMulticaster初始化事件广播器9onRefresh子类扩展钩子如创建 Web 容器10registerListeners注册 ApplicationListener11finishBeanFactoryInitialization实例化所有非懒加载单例 bean12finishRefresh发布刷新完成事件、初始化生命周期处理器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 obtainFreshBeanFactoryBeanDefinition 是怎么被读进来的第二步obtainFreshBeanFactory()在AbstractApplicationContext里是一个模板方法真正的实现在AbstractRefreshableApplicationContextOverride 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委托给XmlBeanDefinitionReaderAnnotationConfigWebApplicationContext和AnnotationConfigApplicationContext则通过ClassPathBeanDefinitionScanner扫描包路径把标了Component、Service、Repository等注解的类解析成ScannedGenericBeanDefinition并注册进容器。这一步完成后DefaultListableBeanFactory的beanDefinitionMap里已经塞满了各种 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负责给实现了ApplicationContextAware、EnvironmentAware、ResourceLoaderAware等接口的 bean 注入对应对象。Spring 之所以拒绝直接用Autowired ApplicationContext而是推荐实现ApplicationContextAware是因为后者的语义更明确、更符合容器规范而Autowired在非 Spring 容器环境下无法工作。ignoreDependencyInterface则是告诉自动装配机制“这几个接口你别管我有专门的处理器来处理”避免重复注入。顺带一提这段代码还注册了systemProperties和systemEnvironment两个单例 bean。这意味着你在业务代码里可以直接Autowired一个名为systemProperties的Map拿到 JVM 系统属性。这个设计很小但能看出 Spring 连“系统级依赖”都统一塞进了容器所有 bean 在同一套依赖体系里平等协作。2.3 后处理器的注册invoke 与 register 的先后差异第五步和第六步是很多初学者容易搞混的地方invokeBeanFactoryPostProcessors和registerBeanPostProcessors到底有什么区别从名字上就能看出关键差异一个是“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 的构造器需要 BB 的构造器需要 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 MapString, Object singletonObjects new ConcurrentHashMap(256); /** 二级缓存存放提前曝光的早期 bean 引用是半成品 */ private final MapString, Object earlySingletonObjects new ConcurrentHashMap(16); /** 三级缓存存放 ObjectFactory用于延迟生成早期引用 */ private final MapString, ObjectFactory? singletonFactories new HashMap(16);缓存级别缓存结构存放内容生命周期一级singletonObjectsConcurrentHashMap完整初始化好的单例 bean也就是getBean的最终产物容器关闭前常驻二级earlySingletonObjectsConcurrentHashMap提前创建出来的原始 bean 实例可能未完成属性填充缓存级别明确防止多线程下重复创建从三级缓存提升后放入三级singletonFactoriesHashMapObjectFactory工厂调用getObject()时才会生成早期引用这里可能生成 AOP 代理在创建完成前一直存在完成后移除三个缓存的读取顺序非常关键先查一级再查二级最后查三级。三级缓存里的ObjectFactory一旦被调用得到的对象会“提升”到二级缓存同时从三级缓存移除也就是说一个 bean 的早期引用在整个创建周期中只会被创建一次。3.3 核心时序A 和 B 是怎么完成互相引用的我用最典型的 A、B 循环依赖场景把整个创建流程逐步拆开。第一步容器准备创建 AgetSingleton(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, allowEarlyReferencetrue)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方法观察singletonObjects、earlySingletonObjects、singletonFactories三个 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这个工厂在生成早期引用时会调用getEarlyBeanReferenceprotected 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 BootSpringBootApplication组合了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 与 postProcessAfterInitializationAbstractAutoProxyCreator里有两条创建代理的路径对应两个来源。路径一循环依赖触发。当 bean 被提前曝光且其他 bean 需要引用它时三级缓存的ObjectFactory.getObject()会调用getEarlyBeanReference。AbstractAutoProxyCreator重写了这个方法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代理发生在postProcessAfterInitializationOverride 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 默认允许循环引用allowCircularReferencestrue如果你明确知道自己不需要循环依赖出于规范考虑可以在配置里关闭它。关闭后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或AsyncAOP 不会生效。因为自调用走的是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依赖导致的。排查建议很简单在postProcessAfterInitialization和getEarlyBeanReference上打断点看看目标 bean 有没有走进wrapIfNecessary以及Advisor是否匹配成功。5.3 源码阅读的一些实用建议如果你打算把refresh()完整啃下来我建议你按这个顺序走先读refresh()骨架配合 debug 看完整 12 步的日志输出。然后在finishBeanFactoryInitialization里打断点进入preInstantiateSingletons。这些方法是在第 11 步里触发的finishBeanFactoryInitialization(beanFactory) - preInstantiateSingletons() - getBean(beanName) - doGetBean() - getSingleton() - createBean() - doCreateBean()doCreateBean是核心中的核心bean 的生命周期基本都能在这里看到实例化、提前曝光、属性填充、初始化、注册销毁方法。循环依赖的缓存变化也主要发生在这个方法及其调用的getSingleton、populateBean中。debug 时我习惯在三个位置加观察表达式singletonObjects、earlySingletonObjects、singletonFactories。你会在 bean 创建过程中直观看到数据在不同缓存里的“搬运”比任何文字描述都管用。还有个经验是源码版本尽量选 5.x 的稳定版比如 Spring Framework 5.3.x。5.3 的代码已经非常成熟读起来结构清晰网上能找到的资料也最多排查问题时容易对照。6. 写在最后的实践体会源码读多了你会发现refresh()里没有太多高深莫测的技巧更多的是对“对象生命周期管理”极其精细的设计。三级缓存和 AOP 代理器的配合本质上是在打包一个庞大的对象图同时还要兼顾循环引用、代理增强、线程安全、懒加载等各种约束条件。我每次读这段源码都会对 Spring 团队的设计能力多一分敬意。一点个人经验如果你是想为面试准备这段内容不必死记方法名而是先掌握“为什么”的脉络。为什么有三级缓存为什么BeanPostProcessor要先注册后执行为什么 AOP 代理器要实现SmartInstantiationAwareBeanPostProcessor这几个问题想通了源码流程自然而然就能串起来。真正去公司面试时面试官看重的是你把机制讲清楚的能力而不是背诵了多少行源码。最后分享一个小技巧读源码卡壳的时候我习惯写一个最简单的 demo只有两个 bean、一个切面、一次断点然后反复重启应用观察调用栈。Spring 源码的调用链虽然长但只要从一个具体入口出发顺着调用栈一层层往回找大多数问题都能在五分钟内定位到对应代码段。这种方式比从头到尾通读源码高效得多也更容易形成自己的理解。
返回列表