循环依赖是指:有个A对象,它的属性是B对象,而B对象的属性也是A对象,在对象创建时就陷入循环了。
我们都知道spring框架是在帮我们管理bean时是可以解决bean之间循环依赖问题的,但是前几天在spring项目中仍然出现了这样的提示
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| client (field private TestLab.JustTalk_Client.src.core.communication.handler.ClientHandlerInitializer TestLab.JustTalk_Client.src.core.communication.Client.clientHandlerInitializer)
↑ ↓
| clientHandlerInitializer (field private TestLab.JustTalk_Client.src.core.communication.handler.ClientHandler TestLab.JustTalk_Client.src.core.communication.handler.ClientHandlerInitializer.clientHandler)
↑ ↓
| clientHandler (field private TestLab.JustTalk_Client.src.core.communication.Client TestLab.JustTalk_Client.src.core.communication.handler.ClientHandler.client)
└─────┘
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.
很明显则表示在spring项目中对于存在循环依赖问题的bean任然没有创建成功。所以spring框架到底能不能解决循环依赖问题呢?
本文本着先解决问题再搞清楚原理的想法,首先接下来将会给出spring项目中几种常见循环依赖问题可能的解决办法,然后在了解spring是怎样解决循环依赖问题的,最后回过头看看为什么这些常见问题会引起循环依赖。
首先spring中只能解决单例模式下的循环依赖问题,需要确认出现问题的bean为单例模式,否则需要考虑重新设计代码结构等方式避免出现循环依赖。
@Component
public class Client {
@Lazy
@Autowired
private ClientHandlerInitializer clientHandlerInitializer;
在此处添加@Lazy注解其实是告诉spring延后初始化这个bean,只有当bean被调用时才会被初始化。使用lazy注解虽然可以隐藏error,但不能从根本上解决这个问题。
@Component
public class Client {
private ClientHandlerInitializer clientHandlerInitializer;
@Autowired
public Client(ClientHandlerInitializer clientHandlerInitializer)
{
this.clientHandlerInitializer = clientHandlerInitializer;
}
Setter/Field方式注入:
@Component
public class Client {
private ClientHandlerInitializer clientHandlerInitializer;
@Autowired
public void setClientHandlerInitializer(ClientHandlerInitializer clientHandlerInitializer)
{
this.clientHandlerInitializer = clientHandlerInitializer;
}
@Component
public class Client {
@Autowired
private ClientHandlerInitializer clientHandlerInitializer;
这样可以方便问题排查,
4. 在配置中允许循环依赖
如果之前的方法都没有效果,并且已经确认需要注入的bean是单例模式,那么就需要考虑配置的问题了。
在 Spring Boot 2.6 开始,Spring Boot 默认禁用了循环依赖,此时需要在配置文件application.properties中配置允许出现循环依赖。
spring.main.allow-circular-references=true
所以spring是怎样解决循环依赖问题的呢?想要知道这个问题首先需要知道spring是怎样帮我们创建和管理对象的。bean的创建过程可以分为两步,实例化和初始化。
实例化阶段:是对象创建的过程。比如使用构造方法new对象,为对象在内存中分配空间,将属性置位默认值。
初始化阶段:为对象中的属性赋值。
循环依赖问题也就发生在bean的初始化阶段,这时就需要引入spring的三级缓存了,三级缓存其实是如下三个map。
package org.springframework.beans.factory.support;
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
private static final int SUPPRESSED_EXCEPTIONS_LIMIT = 100;
/*一级缓存*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
/*三级缓存*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
/*二级缓存*/
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
概述相互依赖bean的创建:
public class A {
public A(B b) {
}
}
public class B {
public B(A a) {
}
}
/*
package org.springframework.beans.factory.support;
protected T doGetBean(String name, @Nullable Class requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException
*/
if (mbd.isSingleton()) {
sharedInstance = this.getSingleton(beanName, () -> {
try {
return this.createBean(beanName, mbd, args);
} catch (BeansException var5) {
this.destroySingleton(beanName);
throw var5;
}
});
beanInstance = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
return this.adaptBeanInstance(name, beanInstance, requiredType);
同时在第三级缓存中放入的是beanName和表达式sharedInstance ;
this.singletonFactories.put(beanName, singletonFactory);
spring解决循环依赖问题便是依靠提早暴露未创建完成的类(实例化完成但还未初始化赋值的状态)实现。
同时,似乎如果要解决循环依赖只需要年级缓存就可以了,为什么会使用三级缓存呢,这其实是为了便于AOP代理的实现,具体原因在本文中就不展开叙述了。
spring通过三级缓存解决循环依赖的过程大致可以如图所示:
BV1ZL411T7bQ
根据上一节对spring三级缓存的分析,那么我们可以回过头来看看为什spring中还会产生无法解决的循环依赖问题。
如果是存在于构造函数中的循环依赖
要使用三级缓存,首先需要能完成bean的实例化,即能够执行构造函数生成一个半成品的bean。所以此时应该尝试将依赖项通过Setter/Field方式实现依赖注入。
多例模式
单例bean的循环引用是因为每个对象都是固定的,只是提前暴露对象的引用,最终这个引用对应的对象是创建完成的。但是多例的情况下,每次getBean都会创建一个新的对象,那么应该引用哪一个对象呢,这本身就已经是矛盾的了。多实例Bean是每次创建都会调用doGetBean方法,根本没有使用一二三级缓存,肯定不能解决循环依赖。因而spring中对于多例之间相互引用是会提示错误的。
end