Spring解决循环依赖问题

Spring解决循环依赖问题

    • 1、spring能不能解决循环依赖
    • 2、spring中循环依赖可能的解决办法
    • 3、spring是怎样解决循环依赖的
    • 4、那些循环依赖不能解决

循环依赖是指:有个A对象,它的属性是B对象,而B对象的属性也是A对象,在对象创建时就陷入循环了。

1、spring能不能解决循环依赖

我们都知道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是怎样解决循环依赖问题的,最后回过头看看为什么这些常见问题会引起循环依赖。

2、spring中循环依赖可能的解决办法

首先spring中只能解决单例模式下的循环依赖问题,需要确认出现问题的bean为单例模式,否则需要考虑重新设计代码结构等方式避免出现循环依赖。

  1. 添加@Lazy注解
    如下所示将@Lazy注解添加到产生循环依赖的对象前,在项目启动时不会报error
@Component
public class Client {

    @Lazy
    @Autowired
    private ClientHandlerInitializer clientHandlerInitializer;

在此处添加@Lazy注解其实是告诉spring延后初始化这个bean,只有当bean被调用时才会被初始化。使用lazy注解虽然可以隐藏error,但不能从根本上解决这个问题。

  1. 采用Setter/Field方式实现依赖注入
    对于所有出现问题的bean采用Setter/Field方式注入,避免采用构造器注入的方式
    构造器注入:
@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;
   }
  1. 依赖注入全部交给spring管理
    对于所有出现问题的bean全部交由spring处理,配置@Autowired注解
@Component
public class Client {
    @Autowired
    private ClientHandlerInitializer clientHandlerInitializer;

这样可以方便问题排查,
4. 在配置中允许循环依赖
如果之前的方法都没有效果,并且已经确认需要注入的bean是单例模式,那么就需要考虑配置的问题了。
在 Spring Boot 2.6 开始,Spring Boot 默认禁用了循环依赖,此时需要在配置文件application.properties中配置允许出现循环依赖。

spring.main.allow-circular-references=true

3、spring是怎样解决循环依赖的

所以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) {
    }
}
  1. 获取A时首先会尝试从一级缓存singletonObjects 中获取;
  2. 获取不到就再从二级缓存earlySingletonObjects中获取;
  3. 若是还没有则尝试从三级缓存singletonFactories获取;
  4. 还是没有获取到则再尝试创建A对象
  5. 会执行doGetBean->createBean->createBeanInstance并使用构造器实例化,如果是单例模式执行过程如下所示
	/*
	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);
  1. 在尝试给A进行初始化时,由于B不存在无法完成初始化,则将半成品A放入第二级缓存中,进入B的创建流程。
  2. 与先前过程相似,在第三级缓存中放入beanName和表达式sharedInstance,进入B的初始化过程
  3. 由于在第二级缓存中可以找到A,则B可以完成初始化,将成品Bean放入一级缓存中备用,删除三级缓存中的B
  4. 同时完成A的初始化,并删除二级缓存中的半成品A

spring解决循环依赖问题便是依靠提早暴露未创建完成的类(实例化完成但还未初始化赋值的状态)实现。
同时,似乎如果要解决循环依赖只需要年级缓存就可以了,为什么会使用三级缓存呢,这其实是为了便于AOP代理的实现,具体原因在本文中就不展开叙述了。
spring通过三级缓存解决循环依赖的过程大致可以如图所示:
BV1ZL411T7bQ

Spring解决循环依赖问题_第1张图片

4、那些循环依赖不能解决

根据上一节对spring三级缓存的分析,那么我们可以回过头来看看为什spring中还会产生无法解决的循环依赖问题。
如果是存在于构造函数中的循环依赖
要使用三级缓存,首先需要能完成bean的实例化,即能够执行构造函数生成一个半成品的bean。所以此时应该尝试将依赖项通过Setter/Field方式实现依赖注入。

多例模式
单例bean的循环引用是因为每个对象都是固定的,只是提前暴露对象的引用,最终这个引用对应的对象是创建完成的。但是多例的情况下,每次getBean都会创建一个新的对象,那么应该引用哪一个对象呢,这本身就已经是矛盾的了。多实例Bean是每次创建都会调用doGetBean方法,根本没有使用一二三级缓存,肯定不能解决循环依赖。因而spring中对于多例之间相互引用是会提示错误的。

end

你可能感兴趣的:(spring,spring,java,spring,boot)