• 深入浅出Spring(28)


    前言

    参考链接1

    问题

    1. 什么是循环依赖?
    2. 如何检测是否存在循环依赖?
    3. 如何解决循环依赖?
    4. 多例能否解决循环依赖?
    5. 单例的情况下,虽然可以解决循环依赖,是否存在其他问题?
    6. 为什么采用三级缓存解决循环依赖?如果直接将早期bean丢到二级缓存可以么?

    1.什么是循环依赖

    A依赖B,B依赖C,C依赖A,形成一个环形。

    2.如何检测是否存在循环依赖?

    单例bean

    检测原理:使用一个列表来记录正在创建中的bean,bean创建之前,先去记录中看一下自己是否已经在列表中了,如果在,说明存在循环依赖,如果不在,则将其加入到这个列表,bean创建完毕之后,将其再从这个列表中移除。

    源码:DefaultSingletonBeanRegistry类中有一个beforeSingletonCreation方法

    	protected void beforeSingletonCreation(String beanName) {
    		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
    			throw new BeanCurrentlyInCreationException(beanName);
    		}
    	}
    	
    	public BeanCurrentlyInCreationException(String beanName) {
    		super(beanName,
    				"Requested bean is currently in creation: Is there an unresolvable circular reference?");
    	}
    	
    	private final Set<String> singletonsCurrentlyInCreation =
    			Collections.newSetFromMap(new ConcurrentHashMap<>(16));
    
    	private final Set<String> inCreationCheckExclusions =
    			Collections.newSetFromMap(new ConcurrentHashMap<>(16));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    我们来看这个表达式,第一个条件是inCreationCheckExclusions不包含这个bean,第二个条件是singletonsCurrentlyInCreation不能添加这个bean,这个时候会抛出一个异常。我们在异常这里打一个断点观察

    在这里插入图片描述
    当前正在创建的set中无法添加这个bean,满足条件二。排除set里面没有值,不包含这个bean,满足第一个条件。

    非单例bean

    AbstractBeanFactory类中的 doGetBean 方法

    	protected <T> T doGetBean(String name, @Nullable Class<T> requiredType,
    	 @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {}
    
    • 1
    • 2

    273 - 275 行

    			if (isPrototypeCurrentlyInCreation(beanName)) {
    				throw new BeanCurrentlyInCreationException(beanName);
    			}
    
    • 1
    • 2
    • 3

    348 - 359 行

    				else if (mbd.isPrototype()) {
    					// It's a prototype -> create a new instance.
    					Object prototypeInstance = null;
    					try {
    						beforePrototypeCreation(beanName);
    						prototypeInstance = createBean(beanName, mbd, args);
    					}
    					finally {
    						afterPrototypeCreation(beanName);
    					}
    					beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
    				}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3.Spring如何解决循环依赖的问题

    spring创建bean主要的几个步骤:

    • 步骤1:实例化bean,即调用构造器创建bean实例

    • 步骤2:填充属性,注入依赖的bean,比如通过set方式、@Autowired注解的方式注入依赖的bean

    • 步骤3:bean的初始化,比如调用init方法等。

    从上面3个步骤中可以看出,注入依赖的对象,有2种情况:

    • 通过步骤1中构造器的方式注入依赖

    • 通过步骤2注入依赖

    构造器方式

    创建三个Service类,让他们循环依赖,a->b->c。下面给出A的示例,其他两个类似。

    @Service
    public class AaService {
    
        BbService bbService;
    
        @Autowired
        public AaService(BbService bbService) {
            this.bbService = bbService;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    启动后会看到下面这样的输出,创建失败。

    在这里插入图片描述

    set方式

    创建三个Service类,让他们循环依赖,d->e->f。下面给出D的示例,其他两个类似。

    @Service
    public class DdService {
    
        EeService eeService;
    
        @Autowired
        public void setEeService(EeService eeService) {
            this.eeService = eeService;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    启动后会看到下面这样的输出,创建失败。

    在这里插入图片描述
    如果将方法上的注解移动到字段上时是可以启动成功的。

    强制忽略循环依赖

    spring:
      main:
        allow-circular-references: true
    
    • 1
    • 2
    • 3

    循环依赖无法解决的情况

    只有单例的bean会通过三级缓存提前暴露来解决循环依赖的问题,而非单例的bean,每次从容器中获取都是一个新的对象,都会重新创建,所以非单例的bean是没有缓存的,不会将其放到三级缓存中。

    探讨:为什么需要用3级缓存

    如果只使用2级缓存,直接将刚实例化好的bean暴露给二级缓存出是否可以否?

    先下个结论吧:不行。

    这样做是可以解决:早期暴露给其他依赖者的bean和最终暴露的bean不一致的问题。

    若将刚刚实例化好的bean直接丢到二级缓存中暴露出去,如果后期这个bean对象被更改了,比如可能在上面加了一些拦截器,将其包装为一个代理了,那么暴露出去的bean和最终的这个bean就不一样的,将自己暴露出去的时候是一个原始对象,而自己最终却是一个代理对象,最终会导致被暴露出去的和最终的bean不是同一个bean的,将产生意向不到的效果,而三级缓存就可以发现这个问题,会报错。

    单例bean解决了循环依赖,还存在什么问题?

    循环依赖的情况下,由于注入的是早期的bean,此时早期的bean中还未被填充属性,初始化等各种操作,也就是说此时bean并没有被完全初始化完毕,此时若直接拿去使用,可能存在有问题的风险。

  • 相关阅读:
    巧记大小端字节序
    【SwitchyOmega】SwitchyOmega 安装及使用
    tp5.1发送阿里云短信验证码
    物联网AI MicroPython传感器学习 之 GC7219点阵屏驱动模块
    每日三题 7.28
    linux根据指定的文件名杀死进程,再定时重启任务
    DASCTF2022.07赋能赛 web 复现
    javaSE笔试题
    从铸剑到御剑:滴滴工程效能平台建设之路
    OGG将Oracle全量同步到kafka
  • 原文地址:https://blog.csdn.net/qq_37151886/article/details/127615778