spring源码系列(一)——spring循环引用

觉得之前那篇阅读性比价差,主要第一次用csdn博客,很多语法不懂,导致文章可读性不好,我彻底更新一下;打算把spring集合写完;
长文警告
正文开始

众所周知spring在默认单例的情况下是支持循环引用的

为了节省图片大小我把那些可以动得gif图片做成了只循环一次,如果看到图片不动了请右键选择在新标签打开,那么图片就会动,手机用户则更简单,直接手指点击图片便能看到动图,每张gif我都标识了,如果没有标识则为静态图片;

Appconfig.java类的代码

@Configurable
@ComponentScan("com.shadow")
public class Appconfig {
}

X.java类的代码

package com.shadow.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class X {
   

	@Autowired
	Y y;

	public X(){
   
		System.out.println("X create");
	}
}

Y.java了的代码

package com.shadow.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Y {
   
	@Autowired
	X x;
	
	public Y(){
   
		System.out.println("Y create");
	}
}

这两个类非常简单,就是相互引用了对方,也就是我们常常的说的循环依赖,spring是允许这样的循环依赖(前提是单例的情况下的,非构造方法注入的情况下)

运行这段代码的结果下图

注意这是张gif,如果你看着不动请参考我上面说的方法

在这里插入图片描述
上面代码从容器中能正常获取到Xbean,说明循环依赖成功。但是spring的循环依赖其实是可以关闭的,spring提供了api来关闭循环依赖的功能。当然你也可以修改spring源码来关闭这个功能,这里笔者为了提高逼格,就修改一下spring的源码来关闭这个功能,老话说:要想高明就得装逼。
下图是我修改spring源码运行的结果
我在AnnotationConfigApplicationContext的构造方法中加了一行setAllowCircularReferences(false);结果代码异常,循环依赖失败

在这里插入图片描述

那么为什么setAllowCircularReferences(false);会关闭循环依赖呢?首要明白spring的循环依赖是怎么做到的呢?spring源码当中是如何处理循环依赖的? 分析一下所谓的循环依赖其实无非就是属性注入,或者就是大家常常说的自动注入, 故而搞明白循环依赖就需要去研究spring自动注入的源码;spring的属性注入属于spring bean的生命周期一部分;怎么理解spring bean的生命周期呢?注意笔者这里并不打算对bean的生命周期大书特书,只是需要读者理解生命周期的概念,细节以后在计较;
要理解bean的生命周期首先记住两个概念
请读者一定记住两个概念——spring bean(一下简称bean)和对象;
1、spring bean——受spring容器管理的对象,可能经过了完整的spring bean生命周期(为什么是可能?难道还有bean是没有经过bean生命周期的?答案是有的,具体我们后面文章分析),最终存在spring容器当中;一个bean一定是个对象
2、对象——任何符合java语法规则实例化出来的对象,但是一个对象并不一定是spring bean;

所谓的bean的生命周期就是磁盘上的类通过spring扫描,然后实例化,跟着初始化,继而放到容器当中的过程;
我画了一张简单图来阐述一下spring bean的生命周期大概有哪些步骤

在这里插入图片描述
上图就是spring容器初始化bean的大概过程(至于详细的过程,后面文章再来介绍);
文字总结一下:
1:实例化一个ApplicationContext的对象;
2:调用bean工厂后置处理器完成扫描;
3:循环解析扫描出来的类信息;
4:实例化一个BeanDefinition对象来存储解析出来的信息;
5:把实例化好的beanDefinition对象put到beanDefinitionMap当中缓存起来,以便后面实例化bean;
6:再次调用bean工厂后置处理器;
7:当然spring还会干很多事情,比如国际化,比如注册BeanPostProcessor等等,如果我们只关心如何实例化一个bean的话那么这一步就是spring调用finishBeanFactoryInitialization方法来实例化单例的bean,实例化之前spring要做验证,需要遍历所有扫描出来的类,依次判断这个bean是否Lazy,是否prototype,是否abstract等等;
8:如果验证完成spring在实例化一个bean之前需要推断构造方法,因为spring实例化对象是通过构造方法反射,故而需要知道用哪个构造方法;
9:推断完构造方法之后spring调用构造方法反射实例化一个对象;注意我这里说的是对象、对象、对象;这个时候对象已经实例化出来了,但是并不是一个完整的bean,最简单的体现是这个时候实例化出来的对象属性是没有注入,所以不是一个完整的bean;
10:spring处理合并后的beanDefinition(合并?是spring当中非常重要的一块内容,后面的文章我会分析);
11:判断是否支持循环依赖,如果支持则提前把一个工厂存入singletonFactories——map;
12:判断是否需要完成属性注入
13:如果需要完成属性注入,则开始注入属性
14:判断bean的类型回调Aware接口
15:调用生命周期回调方法
16:如果需要代理则完成代理
17:put到单例池——bean完成——存在spring容器当中

用一个例子来证明上面的步骤,结合一些运行时期的动态图片

为了节省图片大小我把那些可以动得gif图片做成了只循环一次,如果看到图片不动了请右键选择在新标签打开,那么图片就会动,手机用户则更简单,直接手指点击图片便能看到动图,每张gif我都标识了,如果没有标识则为静态图片;

Z.java的源码

package com.shadow.service;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class Z implements ApplicationContextAware {
   
	@Autowired
	X x;//注入X

    //构造方法
	public Z(){
   
		System.out.println("Z create");
	}

    //生命周期初始化回调方法
	@PostConstruct
	public void zinit(){
   
		System.out.println("call z lifecycle init callback");
	}

	//ApplicationContextAware 回调方法
	@Override
	public void setApplicationContext(ApplicationContext ac) {
   
		System.out.println("call aware callback");
	}
}


来看看Z的生命周期,注意下图当中的字幕,会和上面的17个步骤一一对应
下图是第一步到第六步,请自行对应
接下来我们通过各种图片分析一下springbean的生命周期,读者只需要看图搞明白流程,至于图中涉及的源码,分析完流程之后再来解释;

图① 注意这是张gif,如果你看着不动请参考我上面说的方法

在这里插入图片描述
在研究其他步骤之前,首先了解spring大概在什么时候实例化bean的

图② 注意这是张gif,如果你看着不动请参考我上面说的方法

在这里插入图片描述
上图可以知道spring在AbstractApplicationContext#finishBeanFactoryInitialization方法中完成了bean的实例化。这点需要记住

然后通过图片来说明一下第7

图③ 注意这是张gif,如果你看着不动请参考我上面说的方法

在这里插入图片描述
接下来spring需要推断构造方法,然后通过推断出来的构造方法反射实例化对象,也就是上面说的8步和第9

当然有可能推断不出来构造方法;关于这块知识博主后面更新文章

图④ 注意这是张gif,如果你看着不动请参考我上面说的方法

在这里插入图片描述
上图说明spring是通过createBeanInstance(beanName, mbd, args);完成了推断构造方法和实例化的事情那么接下来便要执行第10步处理合并后的beanDefinition对象,这一块内容特别多,读者可以先不必要理解,后面文章会解释;

图⑤ 注意这是张gif,如果你看着不动请参考我上面说的方法

在这里插入图片描述
仔细看上图,其实这个时候虽然Z被实例化出来了,但是并没有完成属性的注入;其中的X属性为null,而且里面的Aware接口的方法也没有调用,再就是@PostConstruct方法也没有调用,再一次说明他不是一个完整的bean,这里我们只能说z是个对象;
继而applyMergedBeanDefinitionPostProcessors方法就是用来处理合并后的beanDefinition对象;

跟着第11,判断是否支持循环依赖,如果支持则提前暴露一个工厂对象,注意是工厂对象

图⑥ 注意这是张gif,如果你看着不动请参考我上面说的方法

在这里插入图片描述
12步,spring会判断是否需要完成属性注入(spring默认是需要的,但是程序员可以扩展spring,根据情况是否需要完成属性注入);如果需要则spring完成13步——属性注入,也就是所谓的自动注入;

图⑦ 注意这是张gif,如果你看着不动请参考我上面说的方法

在这里插入图片描述
14、15、16

图⑧ 注意这是张gif,如果你看着不动请参考我上面说的方法

在这里插入图片描述
默认情况 至此一个bean完成初始化,被put到单例池,也是对上文说的17个步骤的一个证明;这说明一个bean在spring容器当中被创建出来是有一个过程的,这个过程就是所谓的bean的生命周期,我们的循环依赖也是在这个生命周内完成的。下面我们具体来分析这些步骤

由于be

评论 161
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

子路程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值