spring循环依赖

spring对bean的注入默认是单例模式

什么是循环依赖

在对象的创建过程中,有一种特殊情况,存在可能两个bean之间互相引用,例如下面的TestA中引用了TestB,TestA中引用了TestA,即你中有我,我中有你。

public class TestA {
    //引用了TestB
    private TestB testB;
 
    //省略get,set方法
}
public class TestB {
    //引用了TestA
    private TestA testA;
    
    //省略get,set方法
}

1. bean的注入方式

  1. 构造注入
  2. set注入
  3. @autowrite注入[接口注入]

2.基于这三种注入方式的循环依赖的解决及问题

  1. 通过构造方法注入的类是无法解决循环依赖的,只能抛出BeanCurrentlyInCreationException异常
    在这里插入图片描述
    2.field属性注入(setter方法注入)循环依赖
    三级缓存解决循环依赖,
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
	...
	// 从上至下 分表代表这“三级缓存”
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); //一级缓存
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 二级缓存
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 三级缓存
	...
	
	/** Names of beans that are currently in creation. */
	// 这个缓存也十分重要:它表示bean创建过程中都会在里面呆着~
	// 它在Bean开始创建时放值,创建完成时会将其移出~
	private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));

	/** Names of beans that have already been created at least once. */
	// 当这个Bean被创建完成后,会标记为这个 注意:这里是set集合 不会重复
	// 至少被创建了一次的  都会放进这里~~~~
	private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256));
}

singletonObjects:用于存放完全初始化好的bean,从该缓存中取出的bean可以直接使用
earlySingletonObjects:提前曝光的单例对象的cache,存放原始的bean对象(尚未填充属性),用于解决循环依赖
singletonFactories:单例对象工厂的cache,存放bean工厂对象,用于解决循环依赖

bean注入过程
对Bean的创建最为核心三个方法解释如下:

  • createBeanInstance:例化,其实也就是调用对象的构造方法实例化对象
  • populateBean:填充属性,这一步主要是对bean的依赖属性进行注(@Autowired)
  • initializeBean:回调一些形如initMethod、InitializingBean等方法

从对单例Bean的初始化可以看出,循环依赖主要发生在第二步(populateBean),也就是field属性注入的处理。

1、serviceA调用doGetBean,调用getSingleton发现缓存中没有数据,然后在doCreateBean中对bean进行实例化,实例化先创建serviceA的实例通过createBeanInstance方法去实例化,然后将创建的bean的以一些参数通过objectFactory的函数式编程存到SingletonFactory,然后通过populateBean去对属性的注入,即serviceB的注入。查询earlySingletonObjects 缓存中发现没有数据然后通过getBean去获取serviceB的实例
2、通过doGetBean获取serviceB,调用getSingleton发现缓存中没有数据,然后在doCreateBean中对bean进行实例化,实例化先创建serviceA的实例通过createBeanInstance方法去实例化,然后将创建的bean的以一些参数通过objectFactory的函数式编程存到SingletonFactory,然后通过populateBean去对属性的注入,即serviceA的注入。查询earlySingletonObjects 缓存中发现没有数据然后通过getBean去获取serviceA的实例。
3、通过doGetBean获取serviceA,然后在调用getSingleton从缓存中获取之前存在缓存中的数据最后,首先从singletonObjects中获取数据,然后从earlySingletonObjects中获取数据,最后singletonFactory获取之前第一步保存到这里的数据,返回给第二步需要的serviceA,然后回到第二步中的doCreateBean方法
然后回到第一步的doCreateBean方法中

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

earlySingletonObjects就是getSingleton这个方法中添加数据
核心代码

// bean对象实例创建的核心实现方法
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
		throws BeanCreationException {
		// 省略其他代码
	
		// 1. 调用构造函数创建该bean对象,若不存在构造函数注入,顺利通过
		instanceWrapper = createBeanInstance(beanName, mbd, args);
	

		// 2. 在singletonFactories缓存中,放入该bean对象,以便解决循环依赖问题
		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
	

		// 3. populateBean方法:bean对象的属性赋值
		populateBean(beanName, mbd, instanceWrapper);

		
		// 省略其他代码
	return exposedObject;
}

流程图
流程图
为什么构造方法不能解决循环依赖
因为需要通过这个构造方法去初始化这个实例,但是有部分参数位空获取不到所以循环依赖的两个类都没有实例对象,所以构造注入无法解决循环依赖

另外为了避免循环依赖导致启动问题而又不会解决,有如下建议:

1、业务代码中尽量不要使用构造器注入,即使它有很多优点。
2、业务代码中为了简洁,尽量使用field注入而非setter方法注入
3、若你注入的同时,立马需要处理一些逻辑(一般见于框架设计中,业务代码中不太可能出现),可以使用setter方法注入辅助完成

本文图片以及部分内容来之于:https://blog.csdn.net/f641385712/article/details/92801300

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值