ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Spring Bean IOC、AOP 循环依赖解读:从问题本质到三级缓存源码实现

Spring Bean IOC、AOP 循环依赖解读:从问题本质到三级缓存源码实现 文档教程后端【免费下载链接】CodeGuide:books: 本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总旨在为大家提供一个清晰详细的学习教程侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助请给予支持(关注、点赞、分享)项目地址https://gitcode.com/gh_mirrors/code/CodeGuide点击查看免费下载导读本文以 CodeGuide 仓库中《面经手册 · 第31篇》为核心骨架系统讲解 Spring 中循环依赖的定义、成因与解法。你将掌握循环依赖的三种形态、手写一套可运行的循环依赖解决方案、Spring 从getBean到doCreateBean再到三级缓存getSingleton的核心调用链以及一级、二级、三级缓存各自解决什么问题。文中同时结合仓库内《手撸 Spring》专栏第 13、17 章源码给出可直接对照调试的落地实现与测试案例。一、什么是循环依赖先看清问题的本质了解问题的本质再分析问题往往更利于对问题有更深入的了解和研究。所以在分析 Spring 关于循环依赖的源码之前先要弄清楚什么是循环依赖。1. 循环依赖的三种形态循环依赖主要分为三种情况自身依赖于自身A 的完整创建依赖于 A 自身。互相循环依赖A 依赖 BB 又依赖 A。多组循环依赖A→B→C→A 这类更长链条的环形依赖。但无论循环依赖的数量有多少、链条有多长它的本质都是一样的你的完整创建依赖于我而我的完整创建也依赖于你但我们互相没法解耦最终导致依赖创建失败。所以 Spring 提供了除构造函数注入和原型prototype注入之外的setter 循环依赖注入解决方案。2. 问题体现最原始的循环依赖代码public class ABTest { public static void main(String[] args) { new ClazzA(); } } class ClazzA { private ClazzB b new ClazzB(); } class ClazzB { private ClazzA a new ClazzA(); }这段代码就是循环依赖最初的模样你中有我我中有你运行就会报错java.lang.StackOverflowError。这样的循环依赖代码是没法解决的。当你看到 Spring 中提供 get/set 或者注解之所以能解决循环依赖首先是进行了一定的解耦把类的创建和属性的填充分离。先创建出半成品 Bean再处理属性的填充最终完成成品 Bean 的提供。这一点正是整个循环依赖解决方案的设计基石。3. 问题处理自己动手实现一个循环依赖解决方案在这部分代码中只有一个核心目的自己来解决循环依赖。方案如下public class CircleTest { private final static MapString, Object singletonObjects new ConcurrentHashMap(256); public static void main(String[] args) throws Exception { System.out.println(getBean(B.class).getA()); System.out.println(getBean(A.class).getB()); } private static T T getBean(ClassT beanClass) throws Exception { String beanName beanClass.getSimpleName().toLowerCase(); if (singletonObjects.containsKey(beanName)) { return (T) singletonObjects.get(beanName); } // 实例化对象入缓存 Object obj beanClass.newInstance(); singletonObjects.put(beanName, obj); // 属性填充补全对象 Field[] fields obj.getClass().getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); Class? fieldClass field.getType(); String fieldBeanName fieldClass.getSimpleName().toLowerCase(); field.set(obj, singletonObjects.containsKey(fieldBeanName) ? singletonObjects.get(fieldBeanName) : getBean(fieldClass)); field.setAccessible(false); } return (T) obj; } } class A { private B b; // ...get/set } class B { private A a; // ...get/set }这段代码的要点如下提供了 A、B 两个类互相有依赖。但两个类中的依赖关系使用的是setter 方式进行填充也就是只有这样才能避免两个类在创建之初不非得强依赖于另外一个对象。getBean是整个解决循环依赖的核心内容A 创建后填充属性时依赖 B那么就去创建 B在创建 B 开始填充时发现依赖于 A但此时 A 这个半成品对象已经存放在缓存singletonObjects中了所以 B 可以正常创建再通过递归把 A 也创建完整。这套自研方案与仓库《手撸 Spring》专栏 第17章通过三级缓存解决循环依赖 中仅用一级缓存解决循环依赖的演示实现完全一致——先newInstance创建对象立即入缓存再通过递归填充属性。该章节的测试运行结果也印证了这一点public static void main(String[] args) throws Exception { System.out.println(getBean(B.class).getA()); System.out.println(getBean(A.class).getB()); } cn.bugstack.springframework.test.A49476842 cn.bugstack.springframework.test.B78308db1 Process finished with exit code 0即一级缓存也能解决简单场景的循环依赖问题这也为理解 Spring 三级缓存的必要性做了铺垫。二、Spring 的解决入口getBean → doGetBean有了上面的例子我们已经大概了解到A 和 B 互相依赖时A 创建完后填充属性 B继续创建 B再填充属性 A 时就可以从缓存中获取了。那么把解决循环依赖这件事放到 Spring 中是什么样展开细节虽然解决循环依赖的核心原理一样但要放到支撑起整个 Spring 中 IOC、AOP 特性时就会变得复杂一些。整个处理 Spring 循环依赖的过程基本是入口 getBean → doGetBean → getSingleton查缓存→ createBean → doCreateBean提前暴露→ populateBean填充属性触发递归→ getSingleton三级缓存获取→ registerSingleton注册成品。1. 从单元测试进入源码以下是对 A、B 依赖获取 Bean 的操作重点在于进入getBean的源码跟进Test public void test_alias() { BeanFactory beanFactory new ClassPathXmlApplicationContext(spring-config.xml); Bean_A bean_a beanFactory.getBean(bean_a, Bean_A.class); logger.info(获取 Bean 通过别名{}, bean_a.getBean_b()); }关于 getBean 入口更多的分支细节别名处理、工厂 Bean 前缀、depends-on、父工厂查找、原型抛异常等可结合本仓库《面经手册 · 第30篇》《关于 Spring 中 getBean 的全流程源码解析》 对照学习。2. AbstractBeanFactory#getBean 与 doGetBeanorg.springframework.beans.factory.support.AbstractBeanFactory.javaOverride public T T getBean(String name, ClassT requiredType) throws BeansException { return doGetBean(name, requiredType, null, false); }从 getBean 进入后获取 bean 的操作会进入到 doGetBean。之所以这样包装一层是因为 doGetBean 有很多不同入参的重载方法方便外部操作。doGetBean 方法核心片段protected T T doGetBean( final String name, final ClassT requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { // 从缓存中获取 bean 实例 Object sharedInstance getSingleton(beanName); // mbd.isSingleton() 用于判断 bean 是否是单例模式 if (mbd.isSingleton()) { // 获取 bean 实例 sharedInstance getSingleton(beanName, new ObjectFactoryObject() { Override public Object getObject() throws BeansException { try { // 创建 bean 实例createBean 返回的 bean 实例化好的 return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } } }); // 后续的处理操作 bean getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } // ... // 返回 bean 实例 return (T) bean; }这一部分是从getSingleton先判断是否有实例对象对于第一次进入是肯定没有对象的要继续往下走。在判断mbd.isSingleton()单例以后开始使用基于ObjectFactory包装的方式创建createBean进入后核心逻辑是开始执行doCreateBean操作。三、doCreateBean实例化、提前暴露与属性填充doCreateBean 方法核心片段protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) throws BeanCreationException { // 创建 bean 实例并将 bean 实例包装到 BeanWrapper 对象中返回 instanceWrapper createBeanInstance(beanName, mbd, args); // 添加 bean 工厂对象到 singletonFactories 缓存中 addSingletonFactory(beanName, new ObjectFactoryObject() { Override public Object getObject() throws BeansException { // 获取原始对象的早期引用在 getEarlyBeanReference 方法中会执行 AOP 相关逻辑。若 bean 未被 AOP 拦截getEarlyBeanReference 原样返回 bean。 return getEarlyBeanReference(beanName, mbd, bean); } }); try { // 填充属性解析依赖关系 populateBean(beanName, mbd, instanceWrapper); if (exposedObject ! null) { exposedObject initializeBean(beanName, exposedObject, mbd); } } // 返回 bean 实例 return exposedObject; }在 doCreateBean 方法中包括的内容较多但核心主要是创建实例、加入缓存以及最终进行属性填充属性填充就是把一个 bean 的各个属性字段涉及到的类填充进去createBeanInstance创建 bean 实例并将 bean 实例包装到 BeanWrapper 对象中返回。addSingletonFactory添加 bean 工厂对象到 singletonFactories 缓存中。这一步是解决循环依赖的关键——把刚创建出来的半成品以工厂对象的形式提前暴露出去。getEarlyBeanReference获取原始对象的早期引用。在这个方法中会执行 AOP 相关逻辑若 bean 未被 AOP 拦截则原样返回 bean。populateBean填充属性解析依赖关系。也就是从这开始去找寻 A 实例中属性 B紧接着去创建 B 实例最后再返回回来形成递归。四、核心源码getSingleton 与三级缓存getSingleton 三级缓存核心片段protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 从 singletonObjects 获取实例singletonObjects 是成品 bean Object singletonObject this.singletonObjects.get(beanName); // 判断 beanName isSingletonCurrentlyInCreation 对应的 bean 是否正在创建中 if (singletonObject null isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { // 从 earlySingletonObjects 中获取提前曝光未成品的 bean singletonObject this.earlySingletonObjects.get(beanName); if (singletonObject null allowEarlyReference) { // 获取相应的 bean 工厂 ObjectFactory? singletonFactory this.singletonFactories.get(beanName); if (singletonFactory ! null) { // 提前曝光 bean 实例主要用于解决AOP循环依赖 singletonObject singletonFactory.getObject(); // 将 singletonObject 放入缓存中并将 singletonFactory 从缓存中移除 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return (singletonObject ! NULL_OBJECT ? singletonObject : null); }关键点逐条拆解singletonObjects.get(beanName)从一级缓存获取实例singletonObjects存放的是成品 bean。isSingletonCurrentlyInCreation判断对应 bean 是否正在创建中。只有正在创建中的 bean 才会进入二级、三级缓存查找流程。allowEarlyReference是否允许提前引用为 true 时从二级缓存earlySingletonObjects获取提前曝光未成品的 bean。singletonFactory.getObject()从三级缓存取出工厂对象并执行提前曝光 bean 实例主要用于解决 AOP 循环依赖。拿到真实对象后会放入二级缓存earlySingletonObjects并移除三级缓存中的工厂对象——后续再获取就直接命中二级缓存保证返回的是同一个引用。综上这是一个处理循环依赖的完整代码流程。这部分提取出来的内容主要是核心内容并没有把源码长篇大论的全部拆取出来大家在调试的时候涉及到的代码会比较多建议根据上面的流程多调试几遍逐步建立缓存命中的直觉。五、依赖解析一级、二级、三级缓存分别解决什么1. 一级缓存能解决吗其实只有一级缓存并不是不能解决循环依赖就像我们在前面自己实现的那个例子一样。但是在 Spring 中如果像例子里那么处理就会变得非常麻烦而且也可能出现NPE 问题。按照 Spring 中代码处理的流程去分析一级缓存只存放成品 Bean是不能解决循环依赖问题的。因为 A 的成品创建依赖于 BB 的成品创建又依赖于 A当需要补全 B 的属性时A 还是没有创建完所以会出现死循环。2. 二级缓存能解决吗有了二级缓存这个事处理起来就容易了一个缓存用于存放成品对象另外一个缓存用于存放半成品对象。A 在创建半成品对象后存放到缓存中接下来补充 A 对象中依赖 B 的属性。B 继续创建创建的半成品同样放到缓存中在补充 B 对象的 A 属性时可以从半成品缓存中获取。现在 B 就是一个完整对象了接下来像是递归操作一样A 也变成了完整对象。3. 三级缓存解决什么有了二级缓存都能解决循环依赖了怎么还要三级缓存呢其实在前面分析源码时已经提到过三级缓存主要是解决 Spring AOP 的特性。AOP 本身就是对方法的增强三级缓存中存放的是ObjectFactory?类型的 lambda 表达式工厂对象而 Spring 的原则又不希望将此类类型的 Bean 前置创建所以要存放到三级缓存中延迟处理。整体处理过程类似唯独是B 在填充属性 A 时先查询成品缓存一级、再查半成品缓存二级最后再看看有没有单例工厂类在三级缓存中。最终获取到以后调用getObject方法返回代理引用或者原始引用。至此也就解决了 Spring AOP 所带来的三级缓存问题。关于AOP 代理对象的创建提前到二级缓存仓库《手撸 Spring》专栏 第17章 中有一段清晰的总结如果没有如切面和工厂中的代理对象那么二级缓存也就可以解决了哪怕是只有一级缓存。但为了设计上的合理和可扩展性所以创建了三级缓存来放置不同时期的对象。也就是说三级缓存并非数学上的必须而是在满足 Spring 自身创建原则普通 Bean 全部初始化完成后再处理代理对象前提下的工程最优解。六、仓库落地实现三级缓存在《手撸 Spring》中的源码对应为了让读者对上述 Spring 源码有可对照的完整实现这里直接给出仓库《手撸 Spring》专栏第 17 章 《通过三级缓存解决循环依赖》 中的落地源码。它的处理循环依赖核心流程的类关系包括DefaultSingletonBeanRegistry提供三级缓存singletonObjects成品对象、earlySingletonObjects半成品对象、singletonFactories工厂对象并包装三个缓存提供方法getSingleton、registerSingleton、addSingletonFactory使用方可以分别在不同时间段存放和获取对应的对象。AbstractAutowireCapableBeanFactory#doCreateBean提供提前暴露对象的操作addSingletonFactory(beanName, () - getEarlyBeanReference(beanName, beanDefinition, finalBean))以及后续获取与注册操作exposedObject getSingleton(beanName);、registerSingleton(beanName, exposedObject);。DefaultAdvisorAutoProxyCreator提供的切面服务中实现接口InstantiationAwareBeanPostProcessor新增的getEarlyBeanReference方法便于把依赖的切面对象也能存放到三级缓存中处理对应的 AOP 循环依赖。1. 设置三级缓存cn.bugstack.springframework.beans.factory.support.DefaultSingletonBeanRegistrypublic class DefaultSingletonBeanRegistry implements SingletonBeanRegistry { // 一级缓存普通对象 private MapString, Object singletonObjects new ConcurrentHashMap(); // 二级缓存提前暴漏对象没有完全实例化的对象 protected final MapString, Object earlySingletonObjects new HashMapString, Object(); // 三级缓存存放代理对象 private final MapString, ObjectFactory? singletonFactories new HashMapString, ObjectFactory?(); private final MapString, DisposableBean disposableBeans new LinkedHashMap(); Override public Object getSingleton(String beanName) { Object singletonObject singletonObjects.get(beanName); if (null singletonObject) { singletonObject earlySingletonObjects.get(beanName); // 判断二级缓存中是否有对象这个对象就是代理对象因为只有代理对象才会放到三级缓存中 if (null singletonObject) { ObjectFactory? singletonFactory singletonFactories.get(beanName); if (singletonFactory ! null) { singletonObject singletonFactory.getObject(); // 把三级缓存中的代理对象中的真实对象获取出来放入二级缓存中 earlySingletonObjects.put(beanName, singletonObject); singletonFactories.remove(beanName); } } } return singletonObject; } public void registerSingleton(String beanName, Object singletonObject) { singletonObjects.put(beanName, singletonObject); earlySingletonObjects.remove(beanName); singletonFactories.remove(beanName); } protected void addSingletonFactory(String beanName, ObjectFactory? singletonFactory){ if (!this.singletonObjects.containsKey(beanName)) { this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); } } public void registerDisposableBean(String beanName, DisposableBean bean) { disposableBeans.put(beanName, bean); } }要点说明在用于提供单例对象注册操作的DefaultSingletonBeanRegistry类中共有三个缓存对象的属性singletonObjects、earlySingletonObjects、singletonFactories如它们的名字一样用于存放不同类型的对象单例对象、早期的半成品单例对象、单例工厂对象。在这三个缓存对象下提供了获取、添加和注册不同对象的方法包括getSingleton、registerSingleton、addSingletonFactory。后面两个方法都比较简单主要是getSingleton的操作它是一层层处理不同时期的单例对象直至拿到有效的对象。这与 Spring 官方getSingleton(String beanName, boolean allowEarlyReference)的处理逻辑一一对应。2. 提前暴露对象cn.bugstack.springframework.beans.factory.support.AbstractAutowireCapableBeanFactorypublic abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { protected Object doCreateBean(String beanName, BeanDefinition beanDefinition, Object[] args) { Object bean null; try { // 实例化 Bean bean createBeanInstance(beanDefinition, beanName, args); // 处理循环依赖将实例化后的Bean对象提前放入缓存中暴露出来 if (beanDefinition.isSingleton()) { Object finalBean bean; addSingletonFactory(beanName, () - getEarlyBeanReference(beanName, beanDefinition, finalBean)); } // 实例化后判断 boolean continueWithPropertyPopulation applyBeanPostProcessorsAfterInstantiation(beanName, bean); if (!continueWithPropertyPopulation) { return bean; } // 在设置 Bean 属性之前允许 BeanPostProcessor 修改属性值 applyBeanPostProcessorsBeforeApplyingPropertyValues(beanName, bean, beanDefinition); // 给 Bean 填充属性 applyPropertyValues(beanName, bean, beanDefinition); // 执行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置处理方法 bean initializeBean(beanName, bean, beanDefinition); } catch (Exception e) { throw new BeansException(Instantiation of bean failed, e); } // 注册实现了 DisposableBean 接口的 Bean 对象 registerDisposableBeanIfNecessary(beanName, bean, beanDefinition); // 判断 SCOPE_SINGLETON、SCOPE_PROTOTYPE Object exposedObject bean; if (beanDefinition.isSingleton()) { // 获取代理对象 exposedObject getSingleton(beanName); registerSingleton(beanName, exposedObject); } return exposedObject; } protected Object getEarlyBeanReference(String beanName, BeanDefinition beanDefinition, Object bean) { Object exposedObject bean; for (BeanPostProcessor beanPostProcessor : getBeanPostProcessors()) { if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor) { exposedObject ((InstantiationAwareBeanPostProcessor) beanPostProcessor).getEarlyBeanReference(exposedObject, beanName); if (null exposedObject) return exposedObject; } } return exposedObject; } // ... }要点说明在AbstractAutowireCapableBeanFactory#doCreateBean方法中主要是扩展了对象的提前暴露addSingletonFactory、单例对象的获取getSingleton以及注册registerSingleton三个操作。这里提到的getEarlyBeanReference就是定义在如 AOP 切面中的代理对象处理逻辑可以参考源码中接口InstantiationAwareBeanPostProcessor#getEarlyBeanReference方法的实现。关于 AOP 代理如何通过BeanPostProcessor融入 Bean 生命周期即DefaultAdvisorAutoProxyCreator的职责可对照仓库《手撸 Spring》专栏 第13章《把 AOP 动态代理融入到 Bean 的生命周期》 学习。3. 三层缓存的迁移轨迹综合以上源码一个被循环依赖涉及到的 Bean 在三个缓存中的迁移轨迹可以概括为缓存存放内容何时写入何时取出singletonFactories三级ObjectFactory?工厂对象lambdadoCreateBean实例化后立即addSingletonFactory依赖方查找时执行getObject()提前曝光earlySingletonObjects二级提前曝光的半成品/早期代理对象从三级缓存getObject()后移入依赖方二次查找直接命中singletonObjects一级填充属性并初始化完成的成品对象registerSingleton所有后续getBean直接命中依赖方查找顺序永远是一级 → 二级 → 三级而对象在缓存间的流转方向则是三级工厂→ 二级早期对象→ 一级成品。七、实战验证老公、媳妇、婆婆与切面的循环依赖为了验证三级缓存在普通对象依赖、FactoryBean 代理对象、AOP 切面对象三种场景下都能解决循环依赖仓库第 17 章设计了一个贴近生活的案例老公和媳妇互相依赖婆婆是一个模拟成代理妈妈的职责再配合一个切面来关心家庭生活。1. 测试类与配置老公类Husband依赖媳妇提供查询方法。public class Husband { private Wife wife; public String queryWife(){ return Husband.wife; } }媳妇类Wife依赖老公和婆婆代理妈妈提供查询方法。public class Wife { private Husband husband; private IMother mother; // 婆婆 public String queryHusband() { return Wife.husband、Mother.callMother mother.callMother(); } }婆婆类HusbandMother实现FactoryBeanIMother通过 JDK 动态代理模拟婚后媳妇妈妈的职责被婆婆代理了。public class HusbandMother implements FactoryBeanIMother { Override public IMother getObject() throws Exception { return (IMother) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{IMother.class}, (proxy, method, args) - 婚后媳妇妈妈的职责被婆婆代理了 method.getName()); } }切面类SpouseAdvice实现MethodBeforeAdvice在方法执行前输出关怀信息。public class SpouseAdvice implements MethodBeforeAdvice { Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println(关怀小两口(切面) method); } }spring.xml 属性配置bean idhusband classcn.bugstack.springframework.test.bean.Husband property namewife refwife/ /bean bean idwife classcn.bugstack.springframework.test.bean.Wife property namehusband refhusband/ property namemother refhusbandMother/ /bean bean idhusbandMother classcn.bugstack.springframework.test.bean.HusbandMother/ !-- AOP 配置验证三级缓存 -- bean classcn.bugstack.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator/ bean idbeforeAdvice classcn.bugstack.springframework.test.bean.SpouseAdvice/ bean idmethodInterceptor classcn.bugstack.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor property nameadvice refbeforeAdvice/ /bean bean idpointcutAdvisor classcn.bugstack.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor property nameexpression valueexecution(* cn.bugstack.springframework.test.bean.Wife.*(..))/ property nameadvice refmethodInterceptor/ /bean这里的配置husband依赖wifewife依赖husband和mother工厂代理对象最后是关于 AOP 切面的依赖使用——恰好覆盖了普通对象 FactoryBean 代理 AOP 切面三类需要放入三级缓存的场景。2. 单元测试与运行结果Test public void test_circular() { ClassPathXmlApplicationContext applicationContext new ClassPathXmlApplicationContext(classpath:spring.xml); Husband husband applicationContext.getBean(husband, Husband.class); Wife wife applicationContext.getBean(wife, Wife.class); System.out.println(老公的媳妇 husband.queryWife()); System.out.println(媳妇的老公 wife.queryHusband()); }运行结果老公的媳妇Husband.wife 关怀小两口(切面)public java.lang.String cn.bugstack.springframework.test.bean.Wife.queryHusband() 媳妇的老公Wife.husband、Mother.callMother婚后媳妇妈妈的职责被婆婆代理了callMother Process finished with exit code 0从测试结果可以看到无论是简单对象依赖老公依赖媳妇、媳妇依赖老公还是代理工厂对象或者是 AOP 切面对象都可以在三级缓存下解决循环依赖问题。此外从运行截图DefaultSingletonBeanRegistry#getSingleton中也可以看到凡是需要三级缓存存放工厂对象的类都会使用到getObject获取真实对象并随后存入半成品对象earlySingletonObjects中以及移除工厂对象。八、总结与边界回顾全文先从实际操作的例子开始引导对循环依赖有一个整体的认识也给出可以上手的解决示例这样对后续 Spring 解决循环依赖的方案也就不会那么陌生了。三级缓存并非绝对必须只是在满足 Spring 自身创建原则普通 Bean 全部初始化完成后再处理代理对象的初始化的前提下它是必须的。如果提前创建 AOP 对象并保存到缓存中二级缓存甚至一级缓存也一样可以解决简单场景的循环依赖。三级缓存的本质是延迟 AOP 代理对象的创建时机用空间与一层间接性换取了设计上的优雅、简单与可扩展。Spring 只能解决 setter 注入的循环依赖对于构造函数注入和原型prototype作用域的循环依赖Spring 无法解决——前者在createBeanInstance阶段就形成死锁后者因为每次请求都新建实例不存在提前暴露的复用对象。这也是 《面经手册 · 第30篇》 中强调Spring 只能解决 setter 注入的原因。工程实践建议循环依赖可能并不是一个好的编码方式。如果是在自己的程序中还是要尽可能使用更合理的设计模式规避循环依赖如通过中间层解耦、事件驱动、或者调整依赖方向。这些方式可能会增加代码量但在维护上会更加方便。延伸阅读本仓库中关于 Spring 主题的完整内容还包括《面经手册 · 第29篇》《Spring IOC 特性有哪些不会读不懂源码》 与《手撸 Spring》专栏的 第13章、第17章读者可以按章节顺序从零手写一个支持循环依赖与 AOP 的 Spring 框架获得比阅读本文更深的源码级理解。赞分享文档教程后端【免费下载链接】CodeGuide:books: 本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总旨在为大家提供一个清晰详细的学习教程侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助请给予支持(关注、点赞、分享)项目地址https://gitcode.com/gh_mirrors/code/CodeGuide点击查看免费下载相关推荐Spring IoC 循环依赖源码解析三级缓存与提前暴露机制Spring IoC 循环依赖源码解析三级缓存与提前暴露机制 导读 本文以 Spring Framework 5.3.18 源码为基线完整剖析 IoC文档教程知识库从零手写 SpringCodeGuide 仓库中的渐进式源码实践吃透 IOC、AOP 与三级缓存循环依赖从零手写 SpringCodeGuide 仓库中的渐进式源码实践吃透 IOC、AOP 与三级缓存循环依赖 《手写Spring渐进式源码实践》是 CodeG文档教程后端逐行精讲聚宝盆(Cornucopia)的tuning_train.py损失掩码、梯度累积与DDP训练细节逐行精讲聚宝盆 Cornucopia 的tuning_train.py损失掩码、梯度累积与DDP训练细节 聚宝盆Cornucopia是一个基于 LLaMA人工智能大模型微调LoRANLP金融科技上一篇用 Ollama 本地运行 Qwen 系列模型从一条命令拉取到自定义 GGUF 与工具调用下一篇Project AIRI Web 版入门指南从 Provider 接入、语音交互到角色卡配置创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表