Spring是如何解决循环依赖的

1、什么是循环依赖?

所谓的循环依赖是指,A依赖B,B又依赖A,它们之间形成了相互依赖的关系。或者A依赖B,B依赖C,C又依赖A,这样就形成了一个闭环。它们之间的依赖关系是:
在这里插入图片描述
2、通过手写代码来理解Spring循环依赖:
1)A类中注入了B

@Component
public class InstanceA {

	@Autowired
	private InstanceB instanceB;

	public InstanceB getInstanceB() {
		return instanceB;
	}

	public void sayHello(){
		System.out.println("say hello!");
	}
}

2)B类中依赖A

@Component
public class InstanceB {

	@Autowired
	private InstanceA instanceA;

	public InstanceA getInstanceA() {
		return instanceA;
	}
}

3)测试类

public class MainStat {

	private static Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

	// 一级缓存
	private static Map<String, Object> singletoObjects = new ConcurrentHashMap<>(256);

	/**
	 * 二级缓存,为了将成熟的bean和纯净的bean分离
	 */
	private static Map<String, Object> earlySingletoObjects = new ConcurrentHashMap<>(16);

	/**
	 * 三级缓存
	 */
	private static Map<String, ObjectFactory> singletoFactories = new ConcurrentHashMap<>(256);

	/**
	 * 循环依赖标识
	 */
	public static Set<String> singletonsCurrennlyInCreation = new HashSet<>();


	public static void loadBeanDefinitions(){
		RootBeanDefinition aBeanDefinition = new RootBeanDefinition(InstanceA.class);
		RootBeanDefinition bBeanDefinition = new RootBeanDefinition(InstanceB.class);
		beanDefinitionMap.put("instanceA",aBeanDefinition);
		beanDefinitionMap.put("instanceB",bBeanDefinition);
	}


	/**
	 * 获取bean
	 *
	 * @param beanName
	 * @return
	 */
	public static Object getBean(String beanName) throws Exception {
		// 先尝试从一级缓存中通过beanName取bean,如果可以取到,则直接返回,否则尝试从二级缓存中取
		Object singletoObject = getSingletoObject(beanName);
		if (singletoObject != null) {
			return singletoObject;
		}

		// 正在创建
		if (!singletonsCurrennlyInCreation.contains(beanName)) {
			singletonsCurrennlyInCreation.add(beanName);
		}

		Object instanceBean;
		synchronized (earlySingletoObjects) {

			// 从一级缓存中取,如果取到则直接返回
			if(singletoObjects.containsKey(beanName)){
				return singletoObjects.get(beanName);
			}
			// 实例化
			RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);
			Class<?> beanClass = beanDefinition.getBeanClass();
			// 通过无参构造函数
			instanceBean = beanClass.newInstance();
			// 创建动态代理(此处只会处理A类)
			Object obj = new JdkProxyBeanPostProcessor().getEarlyBeanReference(earlySingletoObjects.get(beanName), beanName);
			// 将代理对象存入到三级缓存中
			singletoFactories.put(beanName, () -> obj);

			// 属性赋值
			Field[] declaredFields = beanClass.getDeclaredFields();
			for (Field declaredField : declaredFields) {
				// 判断该属性上面是否有注解@AutoWired
				Autowired annotation = declaredField.getAnnotation(Autowired.class);
				// 说明属性上面有Autowired
				if (annotation != null) {
					declaredField.setAccessible(true);
					// name = instanceB
					String name = declaredField.getName();
					// 拿到InstanceB得Bean
					Object fileObject = getBean(name);
					declaredField.set(instanceBean, fileObject);
				}
			}

			// 判断二级缓存中是否存在,如果A使用了AOP动态代理,可以确保一级缓存中存入的是AOP代理后的对象,而不是纯净的实例bean
			if (earlySingletoObjects.containsKey(beanName)) {
				instanceBean = earlySingletoObjects.get(beanName);
			}
			// 将得到的bean放入到一级缓存中
			singletoObjects.put(beanName, instanceBean);
		}
		// remove 二级缓存和三级缓存
		return instanceBean;
	}

	/**
	 * 先从一级缓存中取,如果没有获取到再从二级缓存中取
	 * 一级缓存中存的是完整的bean,二级缓存中存入的是独立的bean
	 *
	 * @param beanName
	 * @return
	 */
	public static Object getSingletoObject(String beanName) {
		// 先从一级缓存中获取
		Object bean = singletoObjects.get(beanName);
		// 一级缓存没有并且循环依赖标识set集合中存在(正在创建),则说明是循环依赖
		if (bean == null && singletonsCurrennlyInCreation.contains(beanName)) {
			synchronized (singletoObjects) {
				// 判断二级缓存中是否存在
				bean = earlySingletoObjects.get(beanName);
				if (bean == null) {
					// 从三级缓存中获取
					ObjectFactory factory = singletoFactories.get(beanName);
					if (factory != null) {
						// 拿到动态代理
						bean = factory.getObject();
						// 存入到二级缓存中
						earlySingletoObjects.put(beanName, bean);
					}
				}
			}
		}
		return bean;
	}

	public static void main(String[] args) throws Exception {
		// 加载了BeanDefinition
		loadBeanDefinitions();

		// 循环创建Bean (key:beanName)
		for (String key : beanDefinitionMap.keySet()) {
			// 先创建A
			getBean(key);
		}
		InstanceA instanceA = (InstanceA) getBean("instanceA");
		instanceA.sayHello();

		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
		Car car = applicationContext.getBean("car",Car.class);
		car.say();
	}

}

说明测试类中的大体执行流程

A调用getBean()的流程
1、判断缓存中是否存在
2、开始进来一定不存在,往下走
3、判断循环依赖标识里面是否存在,一定不存在
4、给当前线程上一把锁,并尝试从一级缓存中取,此时一定取不到,接着实例化A,得到实例化的bean
5、再调用自定义的JdkProxyBeanPostProcessor类创建A的动态代理
6、将A的动态代理对象存入到三级缓存中,
7、继续往下执行属性赋值,因为A依赖B,所以B再调用getBean(B)
8B进来重复上面的1,2,3,4(实例化B,5,6步骤
9、因为B中依赖A,所以在进行属性赋值时,A需要调用getBean(A)
10A进入getBean()方法,先从一级缓存中获取
11、一级缓存中没有,并且缓存标识中存在,说明A循环依赖啦,此时给当前线程加一把锁;
再尝试从二级缓存中获取,二级缓存中没有,再从三级缓存中取,因为在
第6步中已经将A的动态代理实例存入到三级缓存中,所以A可以从三级缓存中获取到A动态代理后的实例
12、获取到A动态代理后的实例,则直接返回;
13、再执行下面的代码,判断二级缓存中是否存在,如果A使用了AOP动态代理,可以确保一级缓存中存入的是AOP代理后的对象,而不是纯净的实例bean
14、将得到的bean存入到一级缓存中
------------------------------------------------------
B调用getBean()的流程
1、判断缓存中是否存在
2、开始进来一定不存在,往下走
3、判断循环依赖标识里面是否存在,一定不存在
4、实例化B,得到实例化的bean
5、再调用自定义的JdkProxyBeanPostProcessor类创建动态代理,因为只在A
中使用了AOP,所以B不会创建代理对象
6、存入到三级缓存中的B实例是纯净的
7、执行属性赋值,因为B依赖了A,所以A会调用getBean()方法,由于A在三级缓存中
已经存在,所以在调用getBean()方法时,可以直接获取到A的代理后的实例对象
8、获取到A实例后,B继续往下执行
10、判断二级缓存中是否存在,此时不存在
11、直接将B的实例存入到一级缓存中

2、Spring中的三级缓存?

2.1 一级缓存
// 一级缓存 这个就是我们单例缓存池,用于保存我们所有的单实例bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

作用:保证一个beanName对应的唯一的Bean完整对象

2.2 二级缓存
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

作用:保证一个beanName对应的唯一的Bean不完整对象。
属性暂时没有值的对象称之为不完整的Bean对象(还没有走完生命周期)
比如A、B、C三个类,B、C中依赖 A, A中依赖B、C
在创建A时,保证注入B C,创建时拿到的A的不完整对象是同一个。

2.3 三级缓存
/** 三级缓存 */
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

作用:做一些预备工作。创建bean的时候(第一步实例化产生的对象)先存到三级缓存中。并不直达后面逻辑会不会用,只是防止出现循环依赖且AOP等场景。

  • 为什么需要三级缓存?
    Spring的方法职责都比较单例,一个方法通常只做一件事,getBean就是获取bean 但是调用创建动态代BeanPostProcessor 是属于create的过程中的, 如果在这里明显代码比较耦合,阅读性也不太好。 所以为了解耦、方法职责单一、方便后期维护, 将调用创建动态代理BeanPostProcessor 放在createBean中是最合适不过了, 但是我们判断当前是否循环依赖还是要写在getSingleton里面啊,这怎么办:
    三级缓存 存一个函数接口,函数接口实现 创建动态代理调用BeanPostProcessor。为了避免重复创建, 调用把返回的动态代理对象或者原实例存储在二级缓存,三个缓存完美解决解耦、扩展、性能、代码阅读性。

3、spring中的三级缓存是如何工作的?

在这里插入图片描述

首先是从单单例池(一级缓存)中查找,找到直接使用。
如果一级缓存中没有,尝试从二级缓存中取,找到直接使用。
如果二级缓存中也没有,再尝试从三级缓存中取,如果取到,则将对象放入到二级缓存中。

源代码分析:

@Nullable
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		// Quick check for existing instance without full singleton lock
		/**
		 * 第一步:我们尝试去一级缓存(单例缓存池中去获取对象,一般情况从该map中获取的对象是直接可以使用的)
		 * IOC容器初始化加载单实例bean的时候第一次进来的时候 该map中一般返回空
		 */
		Object singletonObject = this.singletonObjects.get(beanName);
		/**
		 * 若在第一级缓存中没有获取到对象,并且singletonsCurrentlyInCreation这个list包含该beanName
		 * IOC容器初始化加载单实例bean的时候第一次进来的时候 该list中一般返回空,但是循环依赖的时候可以满足该条件
		 */
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			/**
			 * 尝试去二级缓存中获取对象(二级缓存中的对象是一个早期对象)
			 * 何为早期对象:就是bean刚刚调用了构造方法,还来不及给bean的属性进行赋值的对象(纯净态),就是早期对象
			 */
			singletonObject = this.earlySingletonObjects.get(beanName);
			/**
			 * 二级缓存中也没有获取到对象,allowEarlyReference为true(参数是有上一个方法传递进来的true)
			 */
			if (singletonObject == null && allowEarlyReference) {
				synchronized (this.singletonObjects) {
					// Consistent creation of early reference within full singleton lock
					/**尝试从一级缓存中取*/
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						/** 一级缓存中没有,尝试从二级缓存中取*/
						singletonObject = this.earlySingletonObjects.get(beanName);
						if (singletonObject == null) {
							/**二级缓存中没有,则尝试从三级缓存中取
							 */
							ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
							if (singletonFactory != null) {
								/**
								 * 在这里通过暴露的ObjectFactory 包装对象中,通过调用他的getObject()来获取我们的早期对象
								 * 在这个环节中会调用到 getEarlyBeanReference()来进行后置处理
								 */
								singletonObject = singletonFactory.getObject();
								/** 将获取到的代理对象存入到二级缓存中 */
								this.earlySingletonObjects.put(beanName, singletonObject);
								/** 移除三级缓存中的beanName */
								this.singletonFactories.remove(beanName);
							}
						}
					}
				}
			}
		}
		return singletonObject;
	}

4、构造方法和多例导致的循环依赖

4.1 构造方法

上面的代码,使用的都是默认的构造方法来生成的对象,如果是指定特定构造方法,会有什么问题呢?
比如没有用属性注入而是使用构造方法注入,以下代码:

@Component
public class AService {
    private BService bService;

   public AService(BService bService){
       this.bService = bService;
   }
    public void test(){
       System.out.println(this.bService);
   }
}

@Component
public class BService {

    private AService aService;

    public BService(AService aService){
        this.aService = aService;
    }
}

在这里插入图片描述
创建AService的bean对象,只能使用给出的构造方法,但是构造方法里面需要一个BService,发现找不到。
找不到就会去创建BService对象,也是只能使用给出的构造方法,需要一个AService的bean,不能再去创建AService了,但是又没有办法得到一个AService对象。
也就是说,在创建普通对象的时候就失败了,所有没有办法生成lambda表达式,更没有办法做其他事。

  • 解决方法:
    加上@Lazy注解即可。Spring会传入一个@Lazy对应的代理对象参数进入

@Lazy注解的作用:
@Lazy注解用于标识bean是否需要延迟加载

Spring在应用程序上下文启动时去创建所有的单例bean对象,@Lazy注解可以延迟加载bean对象,即在使用时才去初始化.
所以,@Lazy注解, 一是可以减少Spring的IOC容器启动时的加载时间, 二是可以解决bean的循环依赖问题

在这里插入图片描述

4.2 多例
  • 如果是原型bean,那么就意味着每次都要去创建对象,无法利用缓存;
  • 如果是构造方法注入,那么就意味着需要调用构造方法注入,也无法利用缓存。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值