• Spring Bean循环依赖问题及解决


    什么是循环依赖

    类与类之间的依赖关系形成了闭环,就会导致循环依赖问题的产生。举例来说,假设存在两个服务类A和服务类B,如果A通过依赖注入的方式引用了B,且B通过依赖注入的方式引用了A,那么A和B之间就存在循环依赖。
    推广来说,如果涉及多个类,也存在这种依赖关系,那么也是循环依赖问题
    循环依赖问题比较严重,有时会影响服务启动,有时会导致死循环调用(如果线上环境出现循环调用,会导致程序进入死循环,然后服务崩溃,进而导致用户请求无法响应,造成生产事故),应引起足够的重视。

    循环依赖的处理现状

    Spring 支持解决循环依赖,但是仅能支持特定的场景,对于不支持的场景,会在启动时报错。Spring 提供的能力后面会详细重点说明下,在Spring Boot 2.6.0版本开始,默认禁用对循环依赖的支持。也就是说,Spring Boot 2.6.0版本及之后版本,如果存在循环依赖,不管是何种场景,都会报启动错误。启动报错关键信息如下:

    Description:
    
    The dependencies of some of the beans in the application context form a cycle:
    
    ┌─────┐
    |  a (field private com.github.courage007.springbootstarter.service.B com.github.courage007.springbootstarter.service.A.b)
    ↑     ↓
    |  b (field private com.github.courage007.springbootstarter.service.A com.github.courage007.springbootstarter.service.B.a)
    └─────┘
    
    Action:
    
    Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    总结来说,Spring 支持一定程度的循环依赖的解决,但是在2.6.0版本及之后的版本,默认禁用该功能。也就是说,Spring Boot 2.6.0版本及之后版本,如果存在循环依赖,不管是何种场景,都会报启动错误。
    那么Spring 都支持哪些循环依赖的场景呢,后续如果出现循环依赖问题,又该如何解决呢?接下来详细介绍这些内容。

    Spring IoC简介

    在正式介绍 Spring 如何解决 Bean 的循环依赖问题前,先简要介绍下Spring 如何实现Bean的注入。Spring使用IoC技术实现Bean的注入,在具体策略上采用了依赖注入的方式。在Spring中支持的依赖注入实现主要有三种:构造器注入、setter方法注入(也称属性注入)、接口注入等。相对于前两种注入方式,接口注入比繁琐和死板,被注入对象必须声明和实现另外的接口,所以常用的Bean注入方式主要有两种:构造器注入和setter方法注入。

    Spring 如何解决循环依赖

    根据依赖注入的方式不同,可以将循环依赖的场景进行以下划分:

    依赖注入A构造器注入A属性注入
    B构造器注入启动报错,Spring无法自动解决,需要手动解决启动成功,Spring可以自动解决
    B属性注入启动成功,Spring可以自动解决启动成功,Spring可以自动解决

    A构造器注入B,B构造器注入A

    针对A构造器注入B,B构造器注入A的情况,Spring 无法解决这种循环依赖问题,启动时就会报错,报错内容如下:

    ***************************
    APPLICATION FAILED TO START
    ***************************
    
    Description:
    
    The dependencies of some of the beans in the application context form a cycle:
    
    ┌─────┐
    |  a defined in file [/spring-boot-example/spring-boot-web-starter/target/classes/com/github/courage007/springbootwebstarter/resource/A.class]
    ↑     ↓
    |  b defined in file [/spring-boot-example/spring-boot-web-starter/target/classes/com/github/courage007/springbootwebstarter/resource/B.class]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    因为这种循环依赖在启动时就会被发现,所以不会带入到线上环境,影响可控,但错误比较低级。

    A构造器注入B,B属性注入A

    针对A构造器注入B,B构造器注入A的情况,Spring 可以解决这种循环依赖问题,不会影响Spring应用的启动。示例代码如下:

    @Service
    public class A {
        private B b;
    
        public A(B a) {
            this.b = b;
        }
    
        public void test() {
            System.out.println("A");
        }
    }
    
    @Service
    public class B {
        @Autowired
        private A a;
    
        public void test() {
            System.out.println("B");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    虽然Spring可以解决这种循环依赖问题,但是却无法解决方法之间的循环调用,如果同时存在相互调用,则会导致实例不断的被初始化,从而导致栈溢出。示例代码如下:

    @Service
    public class A {
        private B b;
    
        public A(B a) {
            this.b = b;
        }
    
        public void test() {
            System.out.println("A");
            b.test();
        }
    }
    
    @Service
    public class B {
        @Autowired
        private A a;
    
        public void test() {
            System.out.println("B");
            a.test();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    如果客户端调用了实例A的test方法或实例B的test方法,那么将会触发循环调用,进而导致服务崩溃。异常堆栈示例如下:

    *** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message transform method call failed at ./open/src/java.instrument/share/native/libinstrument/JPLISAgent.c line: 872
    2023-11-10 16:46:03.497 ERROR 13643 --- [nio-8082-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.StackOverflowError] with root cause
    
    java.lang.StackOverflowError: null
    	at java.base/java.nio.ByteBuffer.position(ByteBuffer.java:1158) ~[na:na]
    	at java.base/java.nio.ByteBuffer.position(ByteBuffer.java:263) ~[na:na]
    	at java.base/sun.nio.cs.UTF_8.updatePositions(UTF_8.java:80) ~[na:na]
    	at java.base/sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(UTF_8.java:509) ~[na:na]
    	at java.base/sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:564) ~[na:na]
    	at java.base/java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:576) ~[na:na]
    	at java.base/sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:292) ~[na:na]
    	at java.base/sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:281) ~[na:na]
    	at java.base/sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125) ~[na:na]
    	at java.base/java.io.OutputStreamWriter.write(OutputStreamWriter.java:208) ~[na:na]
    	at java.base/java.io.BufferedWriter.flushBuffer(BufferedWriter.java:120) ~[na:na]
    	at java.base/java.io.PrintStream.write(PrintStream.java:605) ~[na:na]
    	at java.base/java.io.PrintStream.print(PrintStream.java:745) ~[na:na]
    	at java.base/java.io.PrintStream.println(PrintStream.java:882) ~[na:na]
    	at com.github.courage007.springbootwebstarter.resource.A.test(A.java:18) ~[classes/:na]
    	at com.github.courage007.springbootwebstarter.resource.B.test(B.java:21) ~[classes/:na]
    	at com.github.courage007.springbootwebstarter.resource.A.test(A.java:19) ~[classes/:na]
    	at com.github.courage007.springbootwebstarter.resource.B.test(B.java:21) ~[classes/:na]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    针对这种情况,因为是在运行时才会发现,如果测试用例覆盖该场景,则不会导致问题流出到现网环境,否则会导致服务崩溃,属于生产事故。

    A属性注入B,B构造器注入A

    针对A属性注入B,B构造器注入A的情况,其分析与A构造器注入B,B构造器注入A一致,保持相同结论即可。

    A属性注入B,B属性注入A

    针对A属性注入B,B属性注入A的情况,其分析与A构造器注入B,B属性注入A相似,保持相同结论即可。这里给出示例代码:

    @Service
    public class A {
        @Autowired
        private B b;
    
        public void test() {
            System.out.println("A");
        }
    }
    
    @Service
    public class B {
        @Autowired
        private A a;
    
        public void test() {
            System.out.println("B");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    源码分析循环依赖

    接下来从Spring Bean的创建过程,看看 Spring 是如何解决一定程度的循环依赖问题。首先,定位到AbstractBeanFactory抽象类的createBean方法。这个方法是Bean创建的入口。
    进入其实现,会进入到AbstractAutowireCapableBeanFactory抽象类(这里仅展示关键源码,完整代码可以执行查看源码)。

    public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
    		implements AutowireCapableBeanFactory {
      // ...
    	@Override
    	protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
    			throws BeanCreationException {
        // ...
        // 优先尝试从代理中获取Bean实例
        Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
        if (bean != null) {
          return bean;
        }
    
    		try {
    			Object beanInstance = doCreateBean(beanName, mbdToUse, args);
    			if (logger.isTraceEnabled()) {
    				logger.trace("Finished creating instance of bean '" + beanName + "'");
    			}
    			return beanInstance;
    		} catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
    			// A previously detected exception with proper bean creation context already,
    			// or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
    			throw ex;
    		} catch (Throwable ex) {
    			throw new BeanCreationException(
    					mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
    		}
    	}
    
      protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
        throws BeanCreationException {
        // 从这几行代码可知,对于多例模式,是会直接创建Bean实例,所以多例模式下,循环依赖问题无法解决
        BeanWrapper instanceWrapper = null;
        if (mbd.isSingleton()) {
          instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
        }
        if (instanceWrapper == null) {
          // 创建bean实例,主要解决构造器注入的场景
          instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
      
        // 缓存的单例集合可以用来解决一定程度的循环依赖问题
        // 启用缓存的场景:(1) 单例场景;(2) 启用允许循环依赖的开关
        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
        isSingletonCurrentlyInCreation(beanName));
        if (earlySingletonExposure) {
          if (logger.isTraceEnabled()) {
            logger.trace("Eagerly caching bean '" + beanName +
                "' to allow for resolving potential circular references");
          }
          addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        }
    
        // Initialize the bean instance.
        Object exposedObject = bean;
        try {
          // 创建bean实例,主要解决属性注入的场景
          populateBean(beanName, mbd, instanceWrapper);
          // bean的实例化
          exposedObject = initializeBean(beanName, exposedObject, mbd);
        }
        catch (Throwable ex) {
          if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
            throw (BeanCreationException) ex;
          }
          else {
            throw new BeanCreationException(
                mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
          }
        }
    
    		if (earlySingletonExposure) {
    			Object earlySingletonReference = getSingleton(beanName, false);
    			if (earlySingletonReference != null) {
    				if (exposedObject == bean) {
    					exposedObject = earlySingletonReference;
    				}
    				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
    					String[] dependentBeans = getDependentBeans(beanName);
    					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
    					for (String dependentBean : dependentBeans) {
    						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
    							actualDependentBeans.add(dependentBean);
    						}
    					}
    					if (!actualDependentBeans.isEmpty()) {
    						throw new BeanCurrentlyInCreationException(beanName,
    								"Bean with name '" + beanName + "' has been injected into other beans [" +
    								StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
    								"] in its raw version as part of a circular reference, but has eventually been " +
    								"wrapped. This means that said other beans do not use the final version of the " +
    								"bean. This is often the result of over-eager type matching - consider using " +
    								"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
    					}
    				}
    			}
    		}
        // ...
        return exposedObject;
    	}
    
      protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    		Object exposedObject = bean;
    		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
    			for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
    				exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
    			}
    		}
    		return exposedObject;
    	}
    }
    
    public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    	// ...
      /** Cache of singleton objects: bean name to bean instance. */
    	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    
      /** Cache of singleton factories: bean name to ObjectFactory. */
    	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
    
    	/** Cache of early singleton objects: bean name to bean instance. */
    	private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
    
    	/** Set of registered singletons, containing the bean names in registration order. */
    	private final Set<String> registeredSingletons = new LinkedHashSet<>(256);
      
    	protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    		Assert.notNull(singletonFactory, "Singleton factory must not be null");
    		synchronized (this.singletonObjects) {
    			if (!this.singletonObjects.containsKey(beanName)) {
    				this.singletonFactories.put(beanName, singletonFactory);
    				this.earlySingletonObjects.remove(beanName);
    				this.registeredSingletons.add(beanName);
    			}
    		}
    	}
    
      protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    		// Quick check for existing instance without full singleton lock
        // 先从一级本地缓存获取对象
    		Object singletonObject = this.singletonObjects.get(beanName);
    		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
          // 如果一级缓存没有,则考虑从二级缓存获取对象
    			singletonObject = this.earlySingletonObjects.get(beanName);
          // 根据是否允许启用allowEarlyReference,再次获取对象
    			if (singletonObject == null && allowEarlyReference) {
    				synchronized (this.singletonObjects) {
    					// Consistent creation of early reference within full singleton lock
    					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) {
    								singletonObject = singletonFactory.getObject();
    								this.earlySingletonObjects.put(beanName, singletonObject);
    								this.singletonFactories.remove(beanName);
    							}
    						}
    					}
    				}
    			}
    		}
    		return singletonObject;
    	}
    
      // ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169

    经过上述源码分析,可知对于Bean多例(Prototype)场景,Spring 是不支持解决循环依赖的,只支持Bean单例(Singleton)的场景。而且Spring还提供了允许循环依赖的开关(默认是关闭的)。在创建Bean的时候,Spring 使用了三级缓存。Spring优先从一级缓存singletonObjects中获取对象;如果没有,则尝试从二级缓存earlySingletonObjects中获取;如果还是没有,就从三级缓存singletonFactory获取对象,然后将对象放置到二级缓存,并从三级缓存溢出该对象。这里只是Spring Bean对象创建的过程,那么Spring 是如何解决Bean的循环依赖的问题呢?
    其实,这里的关键是Spring引入了earlyBeanReference(早期Bean的引用),也就是说在创建Bean的时候,会注入一个还没初始化的对象,从而解决了循环依赖重复创建的问题。这里以A属性注入B,B属性注入A为例,介绍Spring是如何支持循环依赖的。首先在创建A的实例时,会先创建A的对象引用,然后再去查找A依赖的对象,如果不存在,则需要进入依赖对象的创建过程,同样地,会先创建对象的引用,这里是对象B的引用。这样在A中就可以使用B,而B也是类似的操作。
    如果仅使用一级缓存和二级缓存就可以解决循环依赖问题,为什么没有这么做呢?这是因为,并不是需要对所有对象都创建二级缓存,这样可以减少不必要的对象创建,从而节省内存。而引入三级缓存后,如果存在允许循环引用的场景,则会基于对象引用工厂创建对象的引用,然后将其存储到二级缓存。可以说,三级缓存的目的就是为了构造二级缓存中的元素,从而达到节省内存的效果。

    循环依赖的处理

    了解了Spring 支持的循环依赖的解决能力,接下来针对存在循环依赖的场景,给出合适的处理方案。
    如果需要Spring Boot版本从低于2.6.0的版本升级到高于2.6.0的版本,如果现有代码存在循环依赖,且无法再短时间内消减掉该问题,则可以先在配置文件中补充下述配置,做临时规避处理:

    spring:
        main:
            allow-circular-references:true  #允许循环依赖
    
    • 1
    • 2
    • 3

    当然,不建议在新写的代码中引入循环依赖问题。接下来根据是否存在方法循环调用,对问题进一步的划分:不存在循环调用的循环依赖,存在循环调用的循环依赖。
    注意,这里不再关注循环调用的方式,统一使用A属性注入B,B属性注入A这种方式进行说明。

    不存在循环调用的循环依赖

    如果存在循环依赖,但是不存在循环调用,如果是在2.6.0以下版本,不会有问题,只是有引入循环调用的风险;如果是在2.6.0以上的版本,如果不主动开启允许循环依赖的特性开关,是会在启动的时候报循环依赖的错误的。但是如果开启允许循环依赖的特性开关,则不会影响启动,只是有引入循环调用的风险。
    为消减掉循环调用的风险,应尝试解决掉这种循环依赖,这里给出示例代码:

    // 修改前
    @Service
    public class A {
        @Autowired
        private B b;
    
        public void test() {
            System.out.println("A");
            b.test();
        }
    }
    
    @Service
    public class B {
        @Autowired
        private A a;
    
        public void test() {
            System.out.println("B");
        }
    }
    // 修改后
    
    // 修改前
    @Service
    public class A {
        @Autowired
        private C c;
    
        public void test() {
            System.out.println("A");
            c.test();
        }
    }
    
    @Service
    public class B {
        @Autowired
        private A a;
    }
    
    @Service
    public class C {
        public void test() {
            System.out.println("B");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    可见,这里解决掉循环依赖的方式,就是将被依赖的类中的方法,独立包装成一个类,然后被引用。当然,实际的情况可能更复杂,但是核心的处理策略都是一样的。

    存在循环调用的循环依赖

    如果存在循环依赖,且存在循环调用,不管是哪一个Spring 版本,都会因循环调用,导致对象重复创建,进而导致堆栈溢出,引发程序崩溃。从直接现象来看,这是开发人员因逻辑思考有问题,写入了死循环的代码导致。且该代码并没有做自测,如果没有测试用例看护,那么这种问题流出到现网环境,将会带来生产事故。
    为解决这类Bug,就是要思考现有实现,看看代码逻辑,正常来说,可以先将循环调用消减掉,然后再想办法(参考上一节)消减掉循环调用。这里给出存在循环调用的循环依赖的示例代码(之前已有介绍,这里重复一下,方便阅读):

    @Service
    public class A {
        private B b;
    
        public A(B a) {
            this.b = b;
        }
    
        public void test() {
            System.out.println("A");
            b.test();
        }
    }
    
    @Service
    public class B {
        @Autowired
        private A a;
    
        public void test() {
            System.out.println("B");
            a.test();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    总结

    针对Spring Bean的循环依赖问题,如果情况允许,尽量不使用临时规避的手段容忍循环依赖问题。如果因存量代码的问题,尽量将消除循环依赖作为高优先级历史债务进行解决。循环依赖的引入,要么会影响应用的启动,要么会因方法循环调用陷入死循环,从而导致堆栈溢出,给用户带来影响。

    参考

    https://blog.csdn.net/wangxuelei036/article/details/104960558 spring 循环依赖以及解决方案
    https://www.cnblogs.com/daimzh/p/13256413.html 讲一讲Spring中的循环依赖
    https://blog.csdn.net/qq_51076413/article/details/131061348 SpringBoot3.x循环依赖
    https://juejin.cn/post/7032910060044943373 Spring Boot 2.6.0正式发布:默认禁止循环依赖、增强Docker镜像构建
    https://blog.csdn.net/Microhoo_/article/details/132188824 解决Spring Boot 2.6及之后版本取消了循环依赖的支持的问题
    https://zhuanlan.zhihu.com/p/647315070 spring bean 三级缓存

  • 相关阅读:
    easyPOI概述以及原理
    【MySQL】数据类型(二)
    linux-cma&ion
    并发编程之互斥锁
    通过PyTorch构建的LeNet-5网络对手写数字进行训练和识别
    【React-Hooks基础】入门级详解——useState / useEffect /自定义hook
    玉米社:短视频运营主要做哪些工作?
    nodejs的下载安装
    SQL优化相关(持续更新)
    [机器学习] 通俗理解机器学习分类模型评估指标-准确率、精准率、召回率
  • 原文地址:https://blog.csdn.net/wangxufa/article/details/134360210