Spring源码分析之循环依赖

前言

  通过前面对Bean的生命周期的学习,我们肯定以及了解之后,那么这个时候我们们经常就是会听到一个词那么就是循环依赖,这个就是我们这篇文章进行说明的我们都是知道有三级缓存存在可是在Bean的生命周期中我们只用到两个级别的缓存这个那么为什么会出三个缓存呢,这篇文章的就会代你们解决这个迷惑

循环依赖:

  其实循环依赖的话其实就是两个以及两个以上的Bean对象之间相互进行依赖注入:就是A类需要依赖注入B,然后B类的话需要依赖注入A

产生的方式:

  1.通过属性进行依赖注入 2.通过set方法进行依赖注入 3.就是通过构造器进行依赖注入

源码的分析:

 我们有循环依赖的时候由于我们的两个都是第一次加载到Spring容器里面的那么这个时候肯定就是说缓存中都是不存在其对象的那么就可以走下面的这个源码了

前置的知识

通过上面的这个源码我们早就已经见过了格外的熟悉那个public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory)中的 singletonFactory就是一个Lambada表达式其实这个本质就是创建一个Bean对象,下面的话就是查看这个getSinfleton方法的源码:

             //这个就是在Bean创建之前进行进行一次检查
				beforeSingletonCreation(beanName);
				boolean newSingleton = false;
				boolean recordSuppressedExceptions = (locked && this.suppressedExceptions == null);
				if (recordSuppressedExceptions) {
					this.suppressedExceptions = new LinkedHashSet<>();
				}
				try {
					//这个就是创建Bean对象其实就是通过三级缓存中的objectFactory
					singletonObject = singletonFactory.getObject();
					newSingleton = true;
				}

我可以看看beforeSingletonCreation方法:这个就是往singletonsCurrentlyInCreation集合中添加元素

	/** Names of beans that are currently in creation. */
	private final Set<String> singletonsCurrentlyInCreation = ConcurrentHashMap.newKeySet(16);

那么这个时候创建之前的话就往这个集合中添加这个BeanName那么如果创建完成之后是不是也是要从这个集合里面移除(你真的聪明这个就是对的不然的话会出现问题的)

那么当这个Bean对象通过实例化,属性填充,初始话之后那么这个时候就会将这个Bena对象放在了一级缓存中了这个就是通过DefaultSingletonBeanRegistry#addSingleton方法

其实在Bean进行属性注入的时候通过 @Value->autowireCandidate->@Qualifier->@Primary->@Priority进行帅选之后那么这个时候肯定就是说从单例池之后进行获取如果有的话那么就会直接进行返回如果没有的话那么就会进行getSingleton方法进行Bean对象的创建(这个就是要放入到singletonsCurrentlyInCreation集合当中表示的就是说这个Bean正在进行创建)完成之后然后直接放到一级缓存之后并且清除三级缓存以及集合中的BeanName那么发现循环依赖的话那么就是很简单了其实就是通过singletonsCurrentlyInCreation集合是否包含这个BeanName就行了                    下面画图理解一下循环依赖:

其实通过这个就是说Bean对象在创建过程中属性注入中,因为这个注入的Bena的话那么肯定不是完整的Bean对象(其实就是可以理解只是有一个空壳而已而不是说经过了属性填充以及初始化的最终的Bean对象这个和JVM注解创建的对象是不一样的)其实这个也就是很简单的,就是通过构造方法将其中的一个Bean对象进行创建(按照JVM的规范)然后这样的话就是打破了这个循环了,这个就是说只是完成实例化的Bean对象放在那里怎么进行获得这个就是我下面要讲解的内容

Spring解决循环依赖:

 这个就是说在属性注入之前以及实例化之后进行的

//有可能存在的Bean的循环依赖进行一个提前的暴露只能是单例的
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {

			if (logger.isTraceEnabled()) {
				logger.trace("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
			//提前暴露一个单例 Bean 的工厂,并且进行缓存(三级缓存)
			//如果进行属性注入发生循环依赖的时候那么这个时候就通过这个
			//objectFactory中的getObject方法创建对象从而解决循环依赖
			//要实现存在 SmartInstantiationAwareBeanPostProcessor 后处理器接口
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

这个就是会提前进行是否存在循环依赖的判断是不是就是和我之前的思路一样(前提就是说这个Bean是单例的)就是判断那个集合里面是否存在这个BeanName如果存在的话那么就是说这个Bean对象正在创建就是循环依赖了,那么这个就是说通过addSingletonFactory方法来实现的那么下面的话就是来查看这个方法的源码                                                                                         

addSingletonFactory:
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(singletonFactory, "Singleton factory must not be null");
		this.singletonFactories.put(beanName, singletonFactory);
		this.earlySingletonObjects.remove(beanName);
		this.registeredSingletons.add(beanName);
	}

其实这个的话就是通过后面的回调的方法创建出objectFactory然后将其放到singletonFactory这个HashMap中进行存储                                                                                                                          为什么要这么做呢,根据上面我的分析可以知道如果想要打破循环的话那么就是要创建出没有经历过完整生命周期的Bean的实例化对象那么这个的话就是将专门没有经历过完整生命周期的Bean对象那么而这个 singletonFactories 的 Map 集合就是为了缓存这个半成品Bean如果出现循环的话那么这个时候就会使用这个里面的Bean对象, 这样的话就是不会影响到最终的Bean对象的创建(其实判断一个Bean对象是否经历过完整的生命周期的话只要看其是否在单例池(也就是一级缓存)中就行了中是否存在就行了)

但是这个里的话我们就要想一个问题:为什么说就是为什么就是singletonFactories里面不是存放已经完成实例化之后的Bean对象而是objectFactory对象                                                                    接下来就是查看一下getEarlyBeanReference方法

	protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		// 如果Bean不是合成的且存在 SmartInstantiationAwareBeanPostProcessor 后处理器,
		// 则尝试生成代理对象
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
				exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
			}
		}
		return exposedObject;
	}

原来这个也就是说遍历一下后置处理器但是遍历的是SmartInstantiationAwareBeanPostProcessor这个接口那么我们一般说的BeanPosrProcessor这种后置处理器之间的差别:其实就是说这个是其子类的所以在这个实现原本的功能基础之上的化还可以预测Bean的类型,以及提供一个早期引用的bean对象那么这个方法在默认的情况之下就是将Bean对象直接返回

default Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
		return bean;
	}

但是这个方法是有重写的我们一起来看一看:

那么这个时候我们就知道了为什么是放入objectFactory而不是直接放入Bean对象了,因为我们依赖注入的对象也有可能是一个代理对象(如:进行AOP操作的时候),但是我们通过Bean的生命周期就知道其实AOP的操作是在初始化之后但是如果被其它Bean对象引用的话那么肯定是注入的代理对象不然的话就使用不了代理对象的增强方法了

Spring解决方法:

  首先循环依赖的发生肯定就是在Bena的属性注入的时候,然后就是会进行@Lazy进行判断如果有的话那么就会直接进行返回对应的代理对象了之后就是通过getSigton()--去获得相对应的Bean对象->然后就会CeateBean

但是如果循环依赖的话那么我们肯定就是不能走正常的Bean对象的逻辑那么循环依赖的解决的话就是在DefaultSingletonBeanRegistry#getSigton方法当中

// 快速检查一级缓存(singletonObjects),看看是否存在该Bean的实例。
		Object singletonObject = this.singletonObjects.get(beanName);
		 //如果一级缓存中没有的并且这个Bean还是在创建中那么这个时候肯定是出现了循环依赖
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			// 则尝试从二级缓存(earlySingletonObjects)中获取早期引用。
			singletonObject = this.earlySingletonObjects.get(beanName);
			if (singletonObject == null && allowEarlyReference) {
				if (!this.singletonLock.tryLock()) {
					return null;
				}
				try {
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						// 如果一级缓存中仍然没有找到,则尝试从二级缓存中获取。
						singletonObject = this.earlySingletonObjects.get(beanName);
						if (singletonObject == null) {
							// 如果二级缓存中也没有找到,则从三级缓存(singletonFactories)中获取ObjectFactory。
							ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
							if (singletonFactory != null) {
								// 使用ObjectFactory获得Bean实例(这个只是经过实例化),并将其放入二级缓存。
								singletonObject = singletonFactory.getObject();
								//移除第三级缓存中的Bean然后就是将这个Bean放入到二级缓存中
								if (this.singletonFactories.remove(beanName) != null) {
									this.earlySingletonObjects.put(beanName, singletonObject);
								} else {
									singletonObject = this.singletonObjects.get(beanName);
								}
							}
						}
					}
				} 

小总结: 1.就是判断单例池里面存不存在如果存在的话那么就是直接返回 2.就是判断有没有出现循环依赖(这个就是通过集合进行判断) 2.如果出现循环依赖的话那么先从一二级缓存中进行查找,如果都不存在的话就会从 singletonFactories(三级缓存)中获取对应的 ObjectFactory 实例调用getObject 方法去获取只是完成实例化的Bean对象;3.然后将这个Bean对象放入到二级缓存中并且从第三级缓存中进行移除然后将这个对象进行返回就行了 

通过上面的话我们知道其实就是通过singletonFactories以及earlySingletonObjects,因为这个两个的话就是前者就是缓存对象后者的就是进行存放只是进行实例化的Bean对象,那么这二者之间的配合无论是普通对象还是代理对象的循环依赖都能够进行解决

测试:

@Component
public class ADemo {
    @Autowired
    private  BDemo bDemo;
}
@Component
public class BDemo {
    @Autowired
    private ADemo aDemo;
}
//测试使用的代码
  @Test
    public  void test(){
         AnnotationConfigApplicationContext context
                 =new AnnotationConfigApplicationContext(AppConfig.class);
         BDemo bean = (BDemo) context.getBean("BDemo");
         System.out.println(bean);
     }

 测试结果:

  通过上面的学习的话我们就都知道Spring的循环依赖的话就是在Bean对象的属性注入阶段包括如果进行AOP操作的时候生成的代理对象(也就是提前AOP操作)但是正常的情况之下的话AOP的操作的话是在初始化之后进行的那么如果提前创建代理对象的话那么真正进行AOP操作的话怎么进行的下面我们就是来看一段代码就知道了

if (earlySingletonExposure) {
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				}
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					// beanName被哪些bean依赖了,现在发现beanName所对应的bean对象发生了改变,那么则会报错
					String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
					for (String dependentBean : dependentBeans) {
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							actualDependentBeans.add(dependentBean);
						}
					}
					if (!actualDependentBeans.isEmpty()) {
						throw new BeanCurrentlyInCreationException(beanName,
								"Bean with name '" + beanName + "' has been injected into other beans [" +
								StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
								"] in its raw version as part of a circular reference, but has eventually been " +
								"wrapped. This means that said other beans do not use the final version of the " +
								"bean. This is often the result of over-eager type matching - consider using " +
								"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}

就是如果存在循环依赖,那么后面调用 getSingleton 获取到的 earlySingletonReference(二级缓存) 实例对象就不会为空(因为我们已经将将提前被其他的Bean引用的Bean对对象放在其中进行缓存了),这个 earlySingletonReference中缓存的对象可能是普通Bean也可能是 AOP 的代理Bean。
但是在赋值给需要暴露的exposedObject之前,还经过了一个 exposedObject == bean ,这样是去保证循环依赖的出现应该最终得到的Bean也应该和最初反射得到的Bean这个的目的就是为了保证对象的一致性,否则会抛出 BeanCurrentlyInCreationException 异常,表示循环依赖导致出错.

Spring无法解决的循环依赖:

1.Async注解的循环依赖:

  这个就是通过@EnableAsync注解进行开启就是相当于向Spring框架中添加了AsyncAnnotationBeanPostProcessor这个后置处理器这个也是再Bean对象实例化之后使用的这个就是会生成一个代理对象,但是这个对象的话还要进行一些复杂的操作如:访问上下文信息等操作所以这个代理对象操作的话就是一定是要在初始化之后才能进行创建但是这个时候其循环依赖的话其的普通对象已经存在于二级缓存所以这个时候就是出现对象不一致的问题所以就是会出现报错

先写代码测试一手毕竟实践出真理

@Component
public class ADemo {
    @Autowired
    private BDemo BDemo;
}

@Component
public class BDemo {
    @Autowired
    private ADemo demoA;
  //这个就是说这个方法进行异步执行
    @Async
    public  void test(){
        System.out.println("这个是一个测试的方法");
    }
}

//SpringBoot启动的入口处的入口
@SpringBootApplication
@EnableAsync //这个就是能够进行异步化的处理(自动装配的原理)
public class SpringTestDemoApplication {
    public static void main(String[] args) {
       SpringApplication.run(SpringTestDemoApplication.class,args);
    }
}
//测试的代码
   @Test
    public  void test(){
         AnnotationConfigApplicationContext context
                 =new AnnotationConfigApplicationContext(AppConfig.class);
         BDemo bean = (BDemo) context.getBean("BDemo");
     }

报错的情况:

是不是就是说这个ADemo正在创建中,这个正在创建的Bean对象就是BDemo引用的时候创建出来的ADemo的普通对象所以也就就是我上面说的原因

解决方案:

就是说依赖注入BDemo的时候添加一个@Lazy就能解决这个问题了,我们一起来试一试

@Component
public class ADemo {
    @Autowired
    @Lazy
    private BDemo BDemo;
}

最终的效果:

@Lazy注解解决的原因:

就是说当我们启动的时候Spring就是会通过ComponnetScanner里面的注解扫描一些单例的符合条件的类(如 @compnent @Controller)等就会通过getBean方法然后就是通过getSigleton方法生成这个对象的ObjectFactory然后就放在三级缓存进行存储,但是通过这个方式的存放的都是普通对象的objectFactory所以发生循环依赖的话那么就是会通过objectFactory#getObject方法获得普通对象然后放在二级缓存中但是@Lazy注解就是说Spring启动的时候不会对这个类的进行以上的操作而是当他被注入的时候才会进行上面的步骤,那么回到这个里面来说:当ADemo在启动的时候进行getSigton将objectFactory放在三级缓存中,然后进行进行属性填充的时候那么就是进行BDemo的生命周期那么这个时候就能够完成完整的生命周期因为可以在第三级缓存中通过objectFactory中的getObject方法获得ADemo的提前的对象那么就会完成属性注入这一步骤之后就会进行初始化然后初始化完成之后就会通过AsyncAnnotationBeanPostProcessor后置处理器之后就是会生成相关的代理对象然后就会放在单例池中。

2.构造器之间的循环依赖之间的循环依赖:

  这个的话其实也是能够进行很好进行理解的因为我们都知道当我们进行单例Bean的第一次创建的时候那么第一次肯定是要通过构造方法来创建出实例(这一步骤就是通过构造器来完成的)但是如果实例化都完成不了的话那么后面不都是白搭嘛,废话不多说直接进行测试

双边构造器之间的循环依赖:
@Component
public class ADemo {
    private  BDemo bDemo;
    public  ADemo(BDemo bDemo){
        this.bDemo=bDemo;
    }
}
@Component
public class BDemo {
    private ADemo aDemo;
    public BDemo(ADemo aDemo){
        this.aDemo=aDemo;
    }
}
//测试的代码
    @Test
    public  void test(){
         AnnotationConfigApplicationContext context
                 =new AnnotationConfigApplicationContext(AppConfig.class);
         BDemo bean = (BDemo) context.getBean("BDemo");
         System.out.println(bean);
     }

得到的结果:

这个时候的话我就是添加@Lazy注解的话其实也是不能进行解决因为二者都不能完成实例化这一步骤的实现

单边构造器之间的循环依赖:
@Component
public class ADemo {
    @Autowire
    private  BDemo bDemo;
}
@Component
public class BDemo {
    private ADemo aDemo;
    public BDemo(ADemo aDemo){
        this.aDemo=aDemo;
    }
}
//测试的代码
    @Test
    public  void test(){
         AnnotationConfigApplicationContext context
                 =new AnnotationConfigApplicationContext(AppConfig.class);
         BDemo bean = (BDemo) context.getBean("BDemo");
         System.out.println(bean);
     }

测试结果:

还是说是相同的错误的结果但是有一点不一样的就是这个单边构造器循环依赖产生的原因就是说使用构造器依赖注入的Bean对象的话那么这个对象必须是要经历完整的生命周期的是可以进行解决的和上面的Async注解的循环依赖的解决方案是一样的也是添加@Lazy注解就是能够得到充分的解决,原因的话我在上一的话已经说明了,大家可以自行进行测试

@Component
public class ADemo {
    @Autowire
    @Lazy
    private  BDemo bDemo;
}
3.就是Bean对象的作用域不是单例造成的循环依赖:

   这个的话再Spring中已经进行说明,原因就是说如果Bea对象的作用域是prototype那么我们都知道这种类型的Bean对象创建出来之后就是直接进行返回因为每一次创建出来的Bean对象的话都是不一样的那么就是不会存放在三个级别的缓存中


			if (isPrototypeCurrentlyInCreation(beanName)) {
				//如果是多例的循环的依赖的话那么就会直接抛出异常
				throw new BeanCurrentlyInCreationException(beanName);
			}

总结:

  对于一些普通的循环依赖如:属性注入,AOP,Spring已经帮我解决了,但是还是存在有些循环依赖的话Spring还是没有帮我解决,这个时候就要我们自己进行解决,但是在实际使用的时候不要写会造成循环依赖的代码                                                                                                                                    即使要@Async注解以及单边构造器如果有循环依赖的话那么我们都是可以通过@Lazy注解进行解决的

  Spring解决循环依赖的话主要就是通过二级和三级缓存的,那么下面的话就是来说明一下三个缓存之间在循环依赖中都是有什么作用

  SingletonObject(一级缓存):这个里面就是存放的就是经历过完整生命周期的Bean对象,这个也是我们经常说的单例池。

  earlySingletonObjects(二级缓存): 这个里面主要就是存储的就是提前暴露的Bean对象,这个就是说就是说如果出现循环依赖的时候,就会将知识经历过实例化的Bean对象存放在这个里面,但是如果是要进行AOP操作的话那么就是会存放代理对象,不然的话就是存放的就是普通的Bean对象

  singletonFactories:这个就是用于存放ObjectFactory其实这个的本质就是一个Lambada表达式。其实就是说Bean对象实例化之后就会就会基于原先的对象的基础进行一个提前的暴露。如果没有循环依赖的话那么这个时候就是经过一个完整的生命周期然后放在SingletonObject中就行了,如果说出现了循坏依赖的话那么这个对象被其他的对象引用的时候那么就会通过objectFactory中的getObject方法获得一个对象(这个可以就是普通对象也有可能式一个代理对象(这个就是在需要进行AOP的时候))然后将获得的对象放在earlySingletonObjects中进行保存

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值