从源码的角度来看spring的循环引用的问题

标签: spring源码  spring  java

0. 前言

0.1说明

以下的东西,都是我看源码总结出来的,并不是道听途说。大部分都是经过我验证的。如果有错误,那就是我源码没有看明白。有错误的地方,欢迎指出,谢谢。。。。。

0.2 为什么要学习spring 源码?

spring 源码我也看了一段时间了。那为什么要看spring 源码呢?
现在spring 发展的多厉害我不用多说吧,到处都离不开。

都知道:Spring的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对Java设计模式灵活运用以及对Java技术的高深造诣。。它的源代码无意是Java技术的最佳实践的范例。

那spring 的源码体系极其庞大,各种继承实现体系更不用说了。那家伙,能看懂就不错了。还想学习人家是怎么设计的,怎么运用各种设计模式的?我感觉,大部分人,看都费劲,分析人家的结构,还是算了。(除非水平特别的高)。

那我感觉,大部分人,看spring 源码之后,对spring 的流程与设计思想,都有了很深的认识。那我看spring 源码的目的,就是为了对spring更加的了解,更加的精通spring 。并不是说,看了人家的源码,自己写代码的水平就提高了。那也不太可能吧…… 所以说,我看spring源码就是为了对spring 更加的了解。了解其spring 的扩展点等地方,那么有朝一日,你开发了自己的组件,自己的框架,可以很顺利的整合到spring 中。现在,如果一个框架,连spring 都无法整合?你感觉这个框架可以被很多地方运用么?其他的不用多说了吧……

1. 一般情况

那么在了解 “spring如何解决循环引用情况下创建bean的?” 的问题之前呢,我感觉有必要先阐述一下:spring在一般情况下创建bean的大概流程。注意是大概流程!

1.1 图解

在这里插入图片描述

1.2 文字

为了方便进行代码的跳转,和方便阅读,我把图转换成文字。

  1. 进入doGetBean()方法,我们都知道,获取bean是从此方法开始的。

  1. 根据beanName首先去singletonObjects容器(Map)中去拿,如果没有就返回null。
    -----因为此bean并没有处于创建状态(没有在singletonsCurrentlyInCreation中)所以并不会去二级缓存,三级缓存中去拿。 代码详见附录 1

  1. (拿不到的情况)将beanName 放入singletonsCurrentlyInCreation
    ----singletonsCurrentlyInCreation 是一个list 里面存放着正在创建的bean的beanName,当bean创建完成,即将放入singletonObjects中之前,会从中移除。 代码详见 附录2

  1. 选择合适的构造方法对bean进行创建。详见代码附录3

  1. 放入中singletonFactories(三级缓存)
    ----并不是所有的都放入三级缓存,是有条件的;条件是:单例的,允许循环引用的,正在创建状态中的 放入缓存的作用是什么呢?(为了解决循环引用的问题。) 详见代码附录4

  1. 对其内的属性进行填充
    ----- 进行属性注入的时候,如果要注入的对象,还会调用getBean()进行获取(有,则拿到,没有则创建),其流程与这个相同。 如果要注入的对象,其内还依赖了一个其他的对象,则原理相同。详见代码附录 5

  1. 对bean应用相应的BeanPostProcessor,调用相应的Aware 的方法,调用相应的生命周期中的初始化方法等
    -----BeanPostProcessor 这种类型,不用我说,相信都知道,毕竟这东西很重要。
    Aware 看代码:
    生命周期中的初始化的方法比如:你通过xmlor 注解,声明一个 init-method ,或者是afterPropertiesSet()方法等。
    详见代码附录6

  1. beanNamesingletonsCurrentlyInCreation中删除将bean放入singletonObjects, 同时根据beanName将bean从二级缓存三级缓存中删除。
    详见代码附录7

2. 循环引用

2.1 什么是循环引用?

很简单,就是一个对象1 依赖对象2, 但是对象2呢,又依赖对象1。如图所示

在这里插入图片描述

2.2 前置代码准备

准备两个类,让其循环引用,其代码如下:

  • BeanOne
@Component
public class BeanOne {

    @Autowired
    private BeanTwo beanTwo;
}
  • BeanTwo
@Component
public class BeanTwo {

    @Autowired
    private BeanOne beanOne;
}

2.3 流程图解

其代码步骤,在附录都有。希望在看完这个图之后,能理解流程。注意,请跟着箭头的方向看。

在这里插入图片描述

2.4 构造方法注入在循环依赖时产生的问题。

2.4.1 前置代码准备

  • BeanOne
@Component
public class BeanOne {

    private final BeanTwo beanTwo;

    /**
     * 构造方法注入
     */
    @Autowired
    public BeanOne(BeanTwo beanTwo) {
        this.beanTwo = beanTwo;
    }
}
  • BeanTwo
@Component
public class BeanTwo {

    private final BeanOne beanOne;

    /**
     * 构造方法注入
     */
    @Autowired
    public BeanTwo(BeanOne beanOne) {
        this.beanOne = beanOne;
    }
}

  • 测试代码(好像给这个没啥用,哈哈哈)
@Test
public void test3() {
    AnnotationConfigApplicationContext context =
        new AnnotationConfigApplicationContext(AppConfig.class);
}

2.4.2 结果

​ 那结果必定是创建失败的。

2.4.3 为什么?

​ 为什么会失败呢?那我们查看源码,得知的流程为:如果你这边就一个有参的构造方法,他必然会使用你这个有参的构造方法进行对象的创建。

那么流程来了:

1. 准备创建 beanOne 对象:首先将 beanOne的beanName 放入 标识正在创建的list 变量中(singletonsCurrentlyInCreation) 详情看代码附录 2
2. 获得beanOne的构造方法,因为他就一个有参的,用此构造方法进行对象的创建(我已经通过源码验证)
3. 解析beanOne 的唯一一个有参的构造方法, 发现参数中有一个BeanTwo 类型的变量`,则调用getBean()获取BeanTwo类型的变量注入


4. 到了getBean()方法来获取beanTwo 对象了,首先看singletenObjects 等容器中有没有,肯定是没有,因为还没创建
5. 准备创建beanTwo对象: 首先将beanTwo的beanName 放入 标识正在创建的list 变量中(singletonsCurrentlyInCreation) 详情看代码附录 2
6. 获得beanTwo 的构造方法,因为他就一个有参的,用此构造方法进行对象的创建(我已经通过源码验证)
7. 解析 beanTwo 的唯一一个有参的构造方法,发现参数中有一个BeanOne 类型的变量,则调用getBean()获取BeanOne类型的变量注入

8. 到了getBean() 方法来获取beanOne 对象了,首先看singletenObjects 等容器中有没有,肯定是没有,因为还没创建,我们也放不了。
9. 又准备创建beanOne对象:那么重点来了,首先将 beanOne的beanName 放入 标识正在创建的list 变量中(singletonsCurrentlyInCreation) 详情看代码附录 2
然后,你看代码附录2中的代码可以察觉到,我们this.singletonsCurrentlyInCreation.add(beanName) 这句,肯定放不进去,因为有相同的了,所以此语句返回了false, 然后!取反,就是true。然后与另一个语句进行结合。这条语句的判断,就为true ,直接if语句体,然后直接抛了一个异常,然后就没然后了。创建失败。!!结束。

这就是根本原因。

2.5 在循环依赖状态下 使用 构造方法注入与setter(or @Autowired ) 混合的方式产生的问题

​      那我想说的是,就别抽那个风了。这个跟两个对象实例化的顺序有关联,比如 Bean1是使用的是setter or 字段 @Autowired方式注入的。且使用构造方法,能创建出来对象的,那么一创建完成(属性注入之前) 就放到了三级缓存中。

     那么剩下的bean2无论是使用构造方法注入,还是setter方式注入,都不会影响了,因为在bean2 需要bean1 的时候去调用getBean() 方法。然后在getBean() 方法的一开始,就会去缓存拿,那么由于bean1 已经通过构造方法创建了,放入了缓存中,那么必定可以拿到。拿到之后就直接返回了,然后把返回的bean1 直接注入到bean2 中。(虽然现在的bean1中属性还没有注入完整。)

    那么显然再拿bean1 的时候,根本就直接返回了,不会执行后面复杂的逻辑,也就不会执行到 代码附录2 中的那个方法,也就不会抛出异常了(其他的地方检测抛出的,没在论述范围之内)。


那么反过来,那就出问题了。

  1. bean1 放入 标识正在创建的list 变量中(singletonsCurrentlyInCreation) 详情看代码附录 2
  2. bean1使用的是 构造方法注入,然后调用bean1构造方法的时候,发现需要获得bean2. 那么就调用getBean() 来创建bean2
  3. bean2 放入标识正在创建的list 变量中(singletonsCurrentlyInCreation) 详情看代码附录 2
  4. bean2,调用构造方法创建出来了bean2类的实例,然后再调用populateBean() 方法进行bean1的注入,那么再获取bean1的时候,就调用getBean()方法
  5. getBean() 方法获取bean1, 首先去缓存中找,发现没有。因为步骤1bean1根本正在调用构造方法中,根本连实例都没产生出来。所以肯定没有放入缓存,那么去缓存拿,肯定拿不到。之后又进行Bean1的创建
  6. bean1 又要放入标识正在创建的list 变量中(singletonsCurrentlyInCreation) 详情看代码附录 2
  7. 因为在步骤1 里面已经放了,现在再放,肯定会进入 代码附录2 中的if 体里面,自然会抛出异常。
  8. 没了。

2.5 循环依赖中关于aop代理对象的问题

那么有人就问了:这关aop啥事?

      比如这么一个情况,我有两个bean产生了循环依赖。这两个bean都是配置了aop的类,那么问题来了。

bean1 只是调用构造方法实例化了一下,就扔到了三级缓存中了?

还是在这个期间,调用aopBeanPostProcessor 给我们产生一个代理对象,然后把这个代理对象扔到了三级缓存

那么通过观察源码发现,在此期间并没有应用aop。我也不能在这说啥就是啥吧?那么来通过调试证实一下,证明我不是道听途说的(扔了个lambda(ObjectFactory的实现)可以看到bean现在还没有被代理):

在这里插入图片描述

那么排除了上面的,那么就一种可能,就是在从三级缓存中获取的时候,就会调用这个lambda,然后应用对应的BeanPostProceesor 产生aop代理对象。验证:

在这里插入图片描述

3. 总结

那么上面一堆,问:spring 是如何解决循环依赖的问题的呢?

自己根据理解瞎聊就行。(下面总结一下大概)

通过三级缓存解决了循环依赖。

Bean1Bean2 发生循环依赖的时候呢,比如首先实例化的是Bean1,那么首先是去缓存里面取,那么肯定拿不到,然后调用Bean1的相应的构造方法进行Bean1 的实例化,然后再把Bean1的对象放入三级缓存,然后接下来就是调用populateBean()方法注入Bean1对应的属性,注入属性的时候,发现依赖了Bean2则调用getBean() 方法获取Bean2 的对象。


那么就进入了创建Bean2的流程,首先去缓存中拿Bean2, 肯定拿不到。然后调用Bean2对应的构造方法,创建了Bean2的实例,然后放入三级缓存,之后调用populateBean() 方法注入Bean2对应的属性,然后发现依赖了Bean1。那么就该调用getBean() 方法获取Bean1 了。


那么又进入了创建了Bean1的流程,那么这次还是从缓存中拿,发现有了!!!直接返回这个Bean1

然后Bean2初始化完成,然后返回,(虽然引用的Bean1里面的属性还没有注入完成。)

Bean2返回给Bean1 了,所以Bean1 也注入了Bean2

那么此时Bean2 中引用的Bean1也完整了,


解释一下一个基础的问题😂

因为这个是引用啊,就是Bean2 拿的是Bean1 的地址啊。

那Bean1 的属性更新了, 那就都更新了。(这么基础的问题还是解释一下吧)

3.1 注入方式不同对循环依赖的影响

注入方式 结果
两个都是setter/ 字段@Autowired 成功
两个都是构造方法注入 失败
构造方法与 setter/字段@Autowired 混合 先创建的采用setter/字段@Autowired
则可以创建成功,反之不能。

其他的细节就不总结了。

代码附录

1. DefaultSingletonBeanRegistry#getSingleton()


调用位置:

  • doGetBean()->
    • Object sharedInstance = getSingleton(beanName);
      代码:
/**
 * Return the (raw) singleton object registered under the given name.
 * <p>Checks already instantiated singletons and also allows for an early
 * reference to a currently created singleton (resolving a circular reference).
 * @param beanName the name of the bean to look for
 * @param allowEarlyReference whether early references should be created or not
 * @return the registered singleton object, or {@code null} if none found
 */
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	Object singletonObject = this.singletonObjects.get(beanName);
	/*isSingletonCurrentlyInCreation(): 根据beanName 判断此对象是否正在创建(是否在创建的list中)*/
	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;
}

2. DefaultSingletonBeanRegistry#beforeSingletonCreation()


调用位置:

  • doGetBean ->

    • sharedInstance = getSingleton(beanName, () -> {

      • /*将beanName 存入 singletonsCurrentlyInCreation(当前正在创建的bean的名称) list 中*/
        beforeSingletonCreation(beanName);
        

代码:

/**
 * Callback before singleton creation.
 * <p>The default implementation register the singleton as currently in creation.
 * @param beanName the name of the singleton about to be created
 * @see #isSingletonCurrentlyInCreation
 */
protected void beforeSingletonCreation(String beanName) {
	/*
	inCreationCheckExclusions: 存储的是 在当前创建检查中 被排除的bean的名称
	singletonsCurrentlyInCreation: 存储的是  当前正在创建的bean的名称
	*/
	if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
		throw new BeanCurrentlyInCreationException(beanName);
	}
}

3. AbstractAutowireCapableBeanFactory#createBeanInstance


调用位置:

  • AbstractAutowireCapableBeanFactory#doCreateBean

    • instanceWrapper = createBeanInstance(beanName, mbd, args);
      

代码:

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
	// Make sure bean class is actually resolved at this point.
	Class<?> beanClass = resolveBeanClass(mbd, beanName);

	// ..............上面省略

	/*
	需要去确定一个或者多个构造方法。然后利用这些构造方法返回的构造方法 进行bean的创建。
	此方法点进去可以查看他是如何选定合适的构造方法的。
	*/
	// Need to determine the constructor...
	Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
	if (ctors != null ||
			mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||
			mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args))  {
		/*使用自动注入方式的构造方法*/
		return autowireConstructor(beanName, mbd, ctors, args);
	}

	/*使用无参的构造方法直接构造返回*/
	// No special handling: simply use no-arg constructor.
	return instantiateBean(beanName, mbd);
}

4. DefaultSingletonBeanRegistry#addSingletonFactory

调用位置:

  • AbstractAutowireCapableBeanFactory#doCreateBean->

    • /*放入singletonFactories中(三级缓存)*/
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
      

代码:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
	Assert.notNull(singletonFactory, "Singleton factory must not be null");
	synchronized (this.singletonObjects) {
		if (!this.singletonObjects.containsKey(beanName)) {
             /* 加入三级缓存*/
			this.singletonFactories.put(beanName, singletonFactory);
			/* 如果二级缓存中有,则移除*/
             this.earlySingletonObjects.remove(beanName);
			this.registeredSingletons.add(beanName);
		}
	}
}

5. AbstractAutowireCapableBeanFactory#doCreateBean

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
			throws BeanCreationException {
	//.............

	// Initialize the bean instance.
	Object exposedObject = bean;
	try {
         /* 进行属性值的注入*/
		populateBean(beanName, mbd, instanceWrapper);
         /*
		进行bean的初始化:
			应用 BeanPostProcessor(当然也包括aop) , 调用实现了InitializingBean或定义了一个自定义init方法afterPropertiesSet 方法@PostConstruct注解的方法等(生命周期中的方法)
			invokeAwareMethods的调用
		*/
		exposedObject = initializeBean(beanName, exposedObject, mbd);
	}

	//.............
        
}

6. AbstractAutowireCapableBeanFactory#initializeBean()


调用位置 :

  • doCreateBean()->
    • exposedObject = initializeBean(beanName, exposedObject, mbd);

代码:

protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
	if (System.getSecurityManager() != null) {
		AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
			invokeAwareMethods(beanName, bean);
			return null;
		}, getAccessControlContext());
	}
	else {
		/*
		调用Aware 的方法
		例如如果你实现了其特定的Aware 接口的话,会在此调用接口中的方法给其传值

			比如你实现了BeanFactoryAware 接口,他在这会调用其中的setBeanFactory() 方法,
			把方法需要的参数:bean工厂传给你。你就可以用了。

		里面的代码非常的简单,点进去看看,就知道了。
		*/
		invokeAwareMethods(beanName, bean);
	}

	Object wrappedBean = bean;
	if (mbd == null || !mbd.isSynthetic()) {
		/* 应用 BeanPostProcessor 的 postProcessorsBeforeInitialization 方法,
				注意@PostConstruct 注解的方法,也是其中一个BeanPostProcessor 处理的*/
		wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
	}

	try {
		/*如调用afterPropertiesSet(), 自定义的init 方法,*/
		invokeInitMethods(beanName, wrappedBean, mbd);
	}
	catch (Throwable ex) {
		throw new BeanCreationException(
				(mbd != null ? mbd.getResourceDescription() : null),
				beanName, "Invocation of init method failed", ex);
	}
	if (mbd == null || !mbd.isSynthetic()) {
		/*
		应用 BeanPostProcessor 的 processorsAfterInitialization 方法
			如:aop代理对象的生成; 事件发布等;
		*/
		wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
	}

	return wrappedBean;
}

7. DefaultSingletonBeanRegistry#addSingleton


调用位置:

  • doGetBean()

    • sharedInstance = getSingleton(beanName, () -> {
      
      • /* 放入singletonObjects 里面(狭义的ioc容器。)*/
        addSingleton(beanName, singletonObject);
        

代码:

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        this.singletonObjects.put(beanName, singletonObject);
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
    }
}
版权声明:本文为weixin_42041788原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_42041788/article/details/107874761

智能推荐

Thread.join()方法的使用

如果一个线程A执行了thread.join()语句,代表当前线程A等待thread线程终止后才从thread.join()方法返回 并且这个方法具有超时特性,可以添加参数设置 输出结果: jdk中Thread.join()方法的源码(进行了部门调整)   每个线程终止的条件是前驱线程的终止,每个线程等待前驱线程终止后,才从join()方法返回,  当线程终止时,会调用自身的no...

linux服务器部署jenkins笔记

安装jenkins参考文档:https://blog.csdn.net/tomatocc/article/details/83930714 1. 打开jenkins官网:https://jenkins.io/download/ 将war包下载到本地 **ps:**这里要注意的是要下载左边下方的war包,不要下载右边下面的war包。左边是稳定版本,右边是最新版本,建议大家使用稳定版本(我刚开始下载的...

k8s部署elasticsearch集群

百度营销大学     环境准备 我们使用的k8s和ceph环境见: https://blog.51cto.com/leejia/2495558 https://blog.51cto.com/leejia/2499684 ECK简介 Elastic Cloud on Kubernetes,这是一款基于 Kubernetes Operator 模式的新型编排产品,用户可使用该产品在...

saas-export项目-AdminLTE介绍与入门

AdminLTE介绍 (1)AdminLTE是什么? AdminLTE是一款建立在bootstrap和jquery之上的开源的模板主题工具 (2)AdminLTE有什么特点? 提供一系列响应的、可重复使用的组件, 并内置了多个模板页面 自适应多种屏幕分辨率,兼容PC和移动端 快速的创建一个响应式的Html5网站 AdminLTE 不但美观, 而且可以免去写很大CSS与JS的工作量 AdminLTE...

MyBatis中ResultMap结果集映射

用于解决属性名和字段名不一致的情况: resultMap 元素是 MyBatis 中最重要最强大的元素。...

猜你喜欢

编写一个shell

编写shell的过程: 1.从标准输入中读入一个字符串。 2.解析字符串 3.创建一个子进程的执行程序。 4.子进程程序替换。 5.父进程等待子进程退出。...

WEB自动化测试中Xpath定位方法

前言: Xpath是在XML文档中查找信息的一种语言,使用路径表达式来选取XML文档中的节点或节点集,由于XML与HTML结构类似(前者用于传输数据,后者用于显示数据),所以Xpath也常用于查找HTML文档中的节点或节点集。 一  路径表达式: 路径以“/”开始     表示找到满足该绝对路径的元素; 路径以//”开始  ...

力扣困难难度 第4题 寻找两个正序数组的中位数

先看一眼题 我的思路: 设置下标i,j分别用于遍历两个数组,初始值均为0,直到找到两个数组中从小到大的第第length/2个数为止结束循环,length为两个数组长度之和。 ·每次比较nums[i]nums[j],如果前者小则i++,否则j++ ·循环结束时,如果count已经达到length/2,则说明已经找到了中位数,[注意:此时有可能正好其中一个数组遍历完了!所以...

[国家集训队]小Z的袜子(莫队)

[国家集训队]小Z的袜子 题目描述 作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿。终于有一天,小Z再也无法忍受这恼人的找袜子过程,于是他决定听天由命…… 具体来说,小Z把这NN只袜子从1到NN编号,然后从编号LL到RR(LL 尽管小Z并不在意两只袜子是不是完整的一双,甚至不在意两只袜子是否一左一右,他却很在意袜子的颜色,毕竟穿两只不同...

服务器配置(五) 服务器使用tomcat配置https全过程

一.了解服务器配置https协议 HTTPS,是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。 配置HTTPS就需要证书,证书通过权威的CA机构付费获得的证书才能被互联网承认,我们将其放在服务器上面,配置好后,就可以进行https通信了。 通过https访问的网站,在地址前可以看到安全两个字,点击可以查...