Spring循环依赖问题,循环依赖的情况,能解决的情况,怎么解决的

Spring中依赖注入的方式

1、构造器注入
2、setter方法注入
3、接口注入

循环依赖的种类

1、多例模式下的循环注入
2、单例模式下的setter注入
3、单例模式下的构造器注入

Spring能解决哪些循环依赖

多例模式下的循环注入,会导致无限创建对象,最终导致OOM,无法解决
Spring只能解决单例模式下的setter注入导致的循环依赖
单例模式下的构造器注入导致的循环依赖,在实例化对象时就出问题了,先实例化A,还是先实例化B,无法解决。

单例模式下的构造器注入循环依赖问题Spring无法解决

这种情况Spring无法解决

@Component
public class ClassA {
    
    


    private ClassB classB;

    @Autowired
    public ClassA(ClassB classB) {
    
    
        this.classB = classB;
    }

    public ClassB getClassB() {
    
    
        return classB;
    }

    public void setClassB(ClassB classB) {
    
    
        this.classB = classB;
    }
}
@Component
public class ClassB {
    
    

    private ClassA classA;

    @Autowired
    public ClassB(ClassA classA) {
    
    
        this.classA = classA;
    }

    public ClassA getClassA() {
    
    
        return classA;
    }

    public void setClassA(ClassA classA) {
    
    
        this.classA = classA;
    }
    
}

报错信息

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2021-06-13 21:06:57.115 ERROR 11652 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  classA defined in file [F:\javaProject\githubProject\spring-demo\target\classes\com\spring\springdemo\service\ClassA.class]
↑     ↓
|  classB defined in file [F:\javaProject\githubProject\spring-demo\target\classes\com\spring\springdemo\service\ClassB.class]
└─────┘

Process finished with exit code 1

单例模式下的接口注入循环依赖问题Spring能解决

@Component
public class ClassA {
    
    

    @Autowired
    private ClassB classB;

    public ClassB getClassB() {
    
    
        return classB;
    }

    public void setClassB(ClassB classB) {
    
    
        this.classB = classB;
    }
}
@Component
public class ClassB {
    
    

    @Autowired
    private ClassA classA;

    public ClassA getClassA() {
    
    
        return classA;
    }

    public void setClassA(ClassA classA) {
    
    
        this.classA = classA;
    }

}

单例模式下的setter注入循环依赖问题Spring能解决

@Component
public class ClassA {
    
    

    private ClassB classB;

    public ClassB getClassB() {
    
    
        return classB;
    }

    @Autowired
    public void setClassB(ClassB classB) {
    
    
        this.classB = classB;
    }
}
@Component
public class ClassB {
    
    

    private ClassA classA;

    public ClassA getClassA() {
    
    
        return classA;
    }

    @Autowired
    public void setClassA(ClassA classA) {
    
    
        this.classA = classA;
    }
}

多例模式下的循环依赖Spring无法解决

VM参数设置堆大小为一个较小值:-Xmx5m

@Component
@Scope("prototype")
public class ClassA {
    
    

    private ClassB classB;

    public ClassB getClassB() {
    
    
        return classB;
    }

    @Autowired
    public void setClassB(ClassB classB) {
    
    
        this.classB = classB;
    }
}
@Component
@Scope("prototype")
public class ClassB {
    
    

    private ClassA classA;

    public ClassA getClassA() {
    
    
        return classA;
    }

    @Autowired
    public void setClassA(ClassA classA) {
    
    
        this.classA = classA;
    }
}

报错信息如下,这个错误是由于JVM花费太长时间执行GC且只能回收很少的堆内存时抛出的,因为创建了大量对象,而这些对象互相持有导致不能被GC回收。

Exception in thread "RMI TCP Connection(idle)" java.lang.OutOfMemoryError: GC overhead limit exceeded
	at java.lang.Class.getDeclaredMethods0(Native Method)
	at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
	at java.lang.Class.getDeclaredMethod(Class.java:2128)
	at java.io.ObjectStreamClass.getPrivateMethod(ObjectStreamClass.java:1629)
	at java.io.ObjectStreamClass.access$1700(ObjectStreamClass.java:79)
	at java.io.ObjectStreamClass$3.run(ObjectStreamClass.java:520)
	at java.io.ObjectStreamClass$3.run(ObjectStreamClass.java:494)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.io.ObjectStreamClass.<init>(ObjectStreamClass.java:494)
	at java.io.ObjectStreamClass.lookup(ObjectStreamClass.java:391)
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1134)
	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
	at java.io.ObjectOutputStream.defaultWriteObject(ObjectOutputStream.java:441)
	at java.lang.Throwable.writeObject(Throwable.java:985)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:1140)
	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:396)
	at sun.rmi.transport.Transport$1.run(Transport.java:200)
	at sun.rmi.transport.Transport$1.run(Transport.java:197)
	at java.security.AccessController.doPrivileged(Native Method)
	at sun.rmi.transport.Transport.serviceCall(Transport.java:196)
2021-06-13 21:31:41.137  WARN 4956 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanDefinitionStoreException: Failed to process import candidates for configuration class [com.spring.springdemo.SpringDemoApplication]; nested exception is java.lang.OutOfMemoryError: GC overhead limit exceeded
2021-06-13 21:31:41.287  INFO 4956 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2021-06-13 21:31:41.585 ERROR 4956 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.BeanDefinitionStoreException: Failed to process import candidates for configuration class [com.spring.springdemo.SpringDemoApplication]; nested exception is java.lang.OutOfMemoryError: GC overhead limit exceeded
	at org.springframework.context.annotation.ConfigurationClassParser.processImports(ConfigurationClassParser.java:610) ~[spring-context-5.3.7.jar:5.3.7]
	at org.springframework.context.annotation.ConfigurationClassParser.access$800(ConfigurationClassParser.java:111) ~[spring-context-5.3.7.jar:5.3.7]
	at org.springframework.context.annotation.ConfigurationClassParser$DeferredImportSelectorGroupingHandler.lambda$processGroupImports$1(ConfigurationClassParser.java:812) ~[spring-context-5.3.7.jar:5.3.7]
	at java.util.ArrayList.forEach(ArrayList.java:1257) ~[na:1.8.0_211]
	at org.springframework.context.annotation.ConfigurationClassParser$DeferredImportSelectorGroupingHandler.processGroupImports(ConfigurationClassParser.java:809) ~[spring-context-5.3.7.jar:5.3.7]
	at org.springframework.context.annotation.ConfigurationClassParser$DeferredImportSelectorHandler.process(ConfigurationClassParser.java:780) ~[spring-context-5.3.7.jar:5.3.7]
	at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:193) ~[spring-context-5.3.7.jar:5.3.7]
	at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:331) ~[spring-context-5.3.7.jar:5.3.7]
	at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:247) ~[spring-context-5.3.7.jar:5.3.7]
	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:311) ~[spring-context-5.3.7.jar:5.3.7]
	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:112) ~[spring-context-5.3.7.jar:5.3.7]
	at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:746) ~[spring-context-5.3.7.jar:5.3.7]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:564) ~[spring-context-5.3.7.jar:5.3.7]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145) ~[spring-boot-2.5.0.jar:2.5.0]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758) [spring-boot-2.5.0.jar:2.5.0]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:438) [spring-boot-2.5.0.jar:2.5.0]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:337) [spring-boot-2.5.0.jar:2.5.0]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1336) [spring-boot-2.5.0.jar:2.5.0]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1325) [spring-boot-2.5.0.jar:2.5.0]
	at com.spring.springdemo.SpringDemoApplication.main(SpringDemoApplication.java:10) [classes/:na]
Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded


Process finished with exit code 1

Spring怎么解决的单例模式下的setter方法依赖注入引起的循环依赖问题

首先回顾一下上集Spring源码之IoC,单例Bean添加到一、二、三级缓存的时机,源码解读

得到如下结论:

添加到一级缓存:完成Bean的实例化,依赖注入,初始化等操作,生成完整的单例Bean后进行添加。

添加到二级缓存:通过getBean获取实例,在三级缓存中获取到时,添加到二级缓存,并清理三级缓存。

添加到三级缓存:通过createBeanInstance实例化Bean之后,调用populateBean进行依赖注入之前。

解决循环依赖的流程

当ClassA和ClassB相互依赖时,Spring按照如下流程进行依赖注入,循环依赖问题的解决依赖于三级缓存。

1、通过getBean获取ClassA,缓存中不存在ClassA,首先实例化ClassA,并把ClassA加入三级缓存池,对ClassA进行依赖注入时发现ClassA依赖ClassB。

2、通过getBean获取ClassB,缓存中不存在ClassB,首先实例化ClassB,并把ClassB加入三级缓存池,对ClassB进行依赖注入时发现ClassB依赖ClassA。

3、通过getBean获取ClassA,缓存中存在ClassA,直接返回ClassA的引用。

4、ClassB获得ClassA的引用后完成依赖注入、初始化等操作。返回ClassB对象的引用。

5、ClassA获得ClassB的引用后完成依赖注入、初始化等操作。返回ClassA对象的引用。

6、至此,ClassA和ClassB完成了对象的创建,解决了循环依赖问题。

在这里插入图片描述

Spring为什么不能解决构造器的循环依赖?

因为加入缓存的顺序为:三级——>二级——>一级

而加入三级缓存是在实例化Bean后进行的,实例化Bean需要调用构造器,这就要求实例化ClassA的时候,缓存中已经存在ClassB,反过来实例化ClassB的时候,缓存中已经存在ClassA,这是不可能的,所以无法解决构造器循环依赖。

Spring为什么不能解决多例的循环依赖?

多例对象不会放入缓存池,所以必然无法从缓存池中获得对象,而是每次都新建一个对象。
当新建ClassA1对象时,发现依赖ClassB,则新建ClassB1,
新建ClassB1对象时,ClassB1发现依赖ClassA,则又新建一个ClassA2,
当新建ClassA2对象时,发现依赖ClassB,则新建ClassB2,
如此无限循环,创建大量对象,最终会导致OOM内存溢出。

Spring为什么使用三级缓存而不是二级缓存?

参考:https://www.cnblogs.com/semi-sub/p/13548479.html

猜你喜欢

转载自blog.csdn.net/weixin_43073775/article/details/117885591