欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-03-04 17:21:45
...

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三级缓存介绍

一个对象同一时间只能存储在其中一个缓存中。

1、一级缓存:singletonObjects
主要用于存储单例模式下创建完毕的Bean实例,包括该Bean的实例化,依赖注入,实例化等都完成的一个完整的Bean。
该缓存是对外使用的,也就是使用Spring框架的人。

2、二级缓存:earlySingletonObjects
用于存储单例模式下创建中的Bean实例,该Bean被提前暴露的引用
该缓存是对内使用的,指的就是Spring框架内部逻辑使用该缓存。
为了解决第一个classA引用最终如何替换为代理对象的问题(如果有代理对象)

3、三级缓存:singletonFactories
用于存储单例模式下创建中的Bean实例,该Bean被提前暴露的引用
该缓存是对内使用的,指的就是Spring框架内部逻辑使用该缓存。
此缓存是解决循环依赖最大的功臣

getBean方法从缓存中获取实例的源码

获取缓存的顺序:一级缓存——>二级缓存——>三级缓存

    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && this.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 = (ObjectFactory)this.singletonFactories.get(beanName);
                            if (singletonFactory != null) {
                                singletonObject = singletonFactory.getObject();
                                this.earlySingletonObjects.put(beanName, singletonObject);
                                this.singletonFactories.remove(beanName);
                            }
                        }
                    }
                }
            }
        }

        return singletonObject;
    }

参考:关于对spring注入bean的顺序,以及spring如何保证事先加载依赖bean的问题